1 // Copyright 2016 The Draco Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 #include "draco/io/obj_decoder.h"
16 
17 #include <cctype>
18 #include <cmath>
19 
20 #include "draco/io/file_utils.h"
21 #include "draco/io/parser_utils.h"
22 #include "draco/metadata/geometry_metadata.h"
23 
24 namespace draco {
25 
ObjDecoder()26 ObjDecoder::ObjDecoder()
27     : counting_mode_(true),
28       num_obj_faces_(0),
29       num_positions_(0),
30       num_tex_coords_(0),
31       num_normals_(0),
32       num_materials_(0),
33       last_sub_obj_id_(0),
34       pos_att_id_(-1),
35       tex_att_id_(-1),
36       norm_att_id_(-1),
37       material_att_id_(-1),
38       sub_obj_att_id_(-1),
39       deduplicate_input_values_(true),
40       last_material_id_(0),
41       use_metadata_(false),
42       out_mesh_(nullptr),
43       out_point_cloud_(nullptr) {}
44 
DecodeFromFile(const std::string & file_name,Mesh * out_mesh)45 Status ObjDecoder::DecodeFromFile(const std::string &file_name,
46                                   Mesh *out_mesh) {
47   out_mesh_ = out_mesh;
48   return DecodeFromFile(file_name, static_cast<PointCloud *>(out_mesh));
49 }
50 
DecodeFromFile(const std::string & file_name,PointCloud * out_point_cloud)51 Status ObjDecoder::DecodeFromFile(const std::string &file_name,
52                                   PointCloud *out_point_cloud) {
53   std::vector<char> buffer;
54   if (!ReadFileToBuffer(file_name, &buffer)) {
55     return Status(Status::DRACO_ERROR, "Unable to read input file.");
56   }
57   buffer_.Init(buffer.data(), buffer.size());
58 
59   out_point_cloud_ = out_point_cloud;
60   input_file_name_ = file_name;
61   return DecodeInternal();
62 }
63 
DecodeFromBuffer(DecoderBuffer * buffer,Mesh * out_mesh)64 Status ObjDecoder::DecodeFromBuffer(DecoderBuffer *buffer, Mesh *out_mesh) {
65   out_mesh_ = out_mesh;
66   return DecodeFromBuffer(buffer, static_cast<PointCloud *>(out_mesh));
67 }
68 
DecodeFromBuffer(DecoderBuffer * buffer,PointCloud * out_point_cloud)69 Status ObjDecoder::DecodeFromBuffer(DecoderBuffer *buffer,
70                                     PointCloud *out_point_cloud) {
71   out_point_cloud_ = out_point_cloud;
72   buffer_.Init(buffer->data_head(), buffer->remaining_size());
73   return DecodeInternal();
74 }
75 
DecodeInternal()76 Status ObjDecoder::DecodeInternal() {
77   // In the first pass, count the number of different elements in the geometry.
78   // In case the desired output is just a point cloud (i.e., when
79   // out_mesh_ == nullptr) the decoder will ignore all information about the
80   // connectivity that may be included in the source data.
81   counting_mode_ = true;
82   ResetCounters();
83   material_name_to_id_.clear();
84   last_sub_obj_id_ = 0;
85   // Parse all lines.
86   Status status(Status::OK);
87   while (ParseDefinition(&status) && status.ok()) {
88   }
89   if (!status.ok()) {
90     return status;
91   }
92 
93   bool use_identity_mapping = false;
94   if (num_obj_faces_ == 0) {
95     // Mesh has no faces. In this case we try to read the geometry as a point
96     // cloud where every attribute entry is a point.
97 
98     // Ensure the number of all entries is same for all attributes.
99     if (num_positions_ == 0) {
100       return Status(Status::DRACO_ERROR, "No position attribute");
101     }
102     if (num_tex_coords_ > 0 && num_tex_coords_ != num_positions_) {
103       return Status(Status::DRACO_ERROR,
104                     "Invalid number of texture coordinates for a point cloud");
105     }
106     if (num_normals_ > 0 && num_normals_ != num_positions_) {
107       return Status(Status::DRACO_ERROR,
108                     "Invalid number of normals for a point cloud");
109     }
110 
111     out_mesh_ = nullptr;  // Treat the output geometry as a point cloud.
112     use_identity_mapping = true;
113   }
114 
115   // Initialize point cloud and mesh properties.
116   if (out_mesh_) {
117     // Start decoding a mesh with the given number of faces. For point clouds we
118     // silently ignore all data about the mesh connectivity.
119     out_mesh_->SetNumFaces(num_obj_faces_);
120   }
121   if (num_obj_faces_ > 0) {
122     out_point_cloud_->set_num_points(3 * num_obj_faces_);
123   } else {
124     out_point_cloud_->set_num_points(num_positions_);
125   }
126 
127   // Add attributes if they are present in the input data.
128   if (num_positions_ > 0) {
129     GeometryAttribute va;
130     va.Init(GeometryAttribute::POSITION, nullptr, 3, DT_FLOAT32, false,
131             sizeof(float) * 3, 0);
132     pos_att_id_ = out_point_cloud_->AddAttribute(va, use_identity_mapping,
133                                                  num_positions_);
134   }
135   if (num_tex_coords_ > 0) {
136     GeometryAttribute va;
137     va.Init(GeometryAttribute::TEX_COORD, nullptr, 2, DT_FLOAT32, false,
138             sizeof(float) * 2, 0);
139     tex_att_id_ = out_point_cloud_->AddAttribute(va, use_identity_mapping,
140                                                  num_tex_coords_);
141   }
142   if (num_normals_ > 0) {
143     GeometryAttribute va;
144     va.Init(GeometryAttribute::NORMAL, nullptr, 3, DT_FLOAT32, false,
145             sizeof(float) * 3, 0);
146     norm_att_id_ =
147         out_point_cloud_->AddAttribute(va, use_identity_mapping, num_normals_);
148   }
149   if (num_materials_ > 0 && num_obj_faces_ > 0) {
150     GeometryAttribute va;
151     const auto geometry_attribute_type = GeometryAttribute::GENERIC;
152     if (num_materials_ < 256) {
153       va.Init(geometry_attribute_type, nullptr, 1, DT_UINT8, false, 1, 0);
154     } else if (num_materials_ < (1 << 16)) {
155       va.Init(geometry_attribute_type, nullptr, 1, DT_UINT16, false, 2, 0);
156     } else {
157       va.Init(geometry_attribute_type, nullptr, 1, DT_UINT32, false, 4, 0);
158     }
159     material_att_id_ =
160         out_point_cloud_->AddAttribute(va, false, num_materials_);
161 
162     // Fill the material entries.
163     for (int i = 0; i < num_materials_; ++i) {
164       const AttributeValueIndex avi(i);
165       out_point_cloud_->attribute(material_att_id_)->SetAttributeValue(avi, &i);
166     }
167 
168     if (use_metadata_) {
169       // Use metadata to store the name of materials.
170       std::unique_ptr<AttributeMetadata> material_metadata =
171           std::unique_ptr<AttributeMetadata>(new AttributeMetadata());
172       material_metadata->AddEntryString("name", "material");
173       // Add all material names.
174       for (const auto &itr : material_name_to_id_) {
175         material_metadata->AddEntryInt(itr.first, itr.second);
176       }
177       if (!material_file_name_.empty()) {
178         material_metadata->AddEntryString("file_name", material_file_name_);
179       }
180 
181       out_point_cloud_->AddAttributeMetadata(material_att_id_,
182                                              std::move(material_metadata));
183     }
184   }
185   if (!obj_name_to_id_.empty() && num_obj_faces_ > 0) {
186     GeometryAttribute va;
187     if (obj_name_to_id_.size() < 256) {
188       va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT8, false, 1, 0);
189     } else if (obj_name_to_id_.size() < (1 << 16)) {
190       va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT16, false, 2, 0);
191     } else {
192       va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT32, false, 4, 0);
193     }
194     sub_obj_att_id_ = out_point_cloud_->AddAttribute(
195         va, false, static_cast<uint32_t>(obj_name_to_id_.size()));
196     // Fill the sub object id entries.
197     for (const auto &itr : obj_name_to_id_) {
198       const AttributeValueIndex i(itr.second);
199       out_point_cloud_->attribute(sub_obj_att_id_)->SetAttributeValue(i, &i);
200     }
201     if (use_metadata_) {
202       // Use metadata to store the name of materials.
203       std::unique_ptr<AttributeMetadata> sub_obj_metadata =
204           std::unique_ptr<AttributeMetadata>(new AttributeMetadata());
205       sub_obj_metadata->AddEntryString("name", "sub_obj");
206       // Add all sub object names.
207       for (const auto &itr : obj_name_to_id_) {
208         const AttributeValueIndex i(itr.second);
209         sub_obj_metadata->AddEntryInt(itr.first, itr.second);
210       }
211       out_point_cloud_->AddAttributeMetadata(sub_obj_att_id_,
212                                              std::move(sub_obj_metadata));
213     }
214   }
215 
216   // Perform a second iteration of parsing and fill all the data.
217   counting_mode_ = false;
218   ResetCounters();
219   // Start parsing from the beginning of the buffer again.
220   buffer()->StartDecodingFrom(0);
221   while (ParseDefinition(&status) && status.ok()) {
222   }
223   if (!status.ok()) {
224     return status;
225   }
226   if (out_mesh_) {
227     // Add faces with identity mapping between vertex and corner indices.
228     // Duplicate vertices will get removed later.
229     Mesh::Face face;
230     for (FaceIndex i(0); i < num_obj_faces_; ++i) {
231       for (int c = 0; c < 3; ++c) {
232         face[c] = 3 * i.value() + c;
233       }
234       out_mesh_->SetFace(i, face);
235     }
236   }
237 
238 #ifdef DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED
239   if (deduplicate_input_values_) {
240     out_point_cloud_->DeduplicateAttributeValues();
241   }
242 #endif
243 #ifdef DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED
244   out_point_cloud_->DeduplicatePointIds();
245 #endif
246   return status;
247 }
248 
ResetCounters()249 void ObjDecoder::ResetCounters() {
250   num_obj_faces_ = 0;
251   num_positions_ = 0;
252   num_tex_coords_ = 0;
253   num_normals_ = 0;
254   last_material_id_ = 0;
255   last_sub_obj_id_ = 0;
256 }
257 
ParseDefinition(Status * status)258 bool ObjDecoder::ParseDefinition(Status *status) {
259   char c;
260   parser::SkipWhitespace(buffer());
261   if (!buffer()->Peek(&c)) {
262     // End of file reached?.
263     return false;
264   }
265   if (c == '#') {
266     // Comment, ignore the line.
267     parser::SkipLine(buffer());
268     return true;
269   }
270   if (ParseVertexPosition(status)) {
271     return true;
272   }
273   if (ParseNormal(status)) {
274     return true;
275   }
276   if (ParseTexCoord(status)) {
277     return true;
278   }
279   if (ParseFace(status)) {
280     return true;
281   }
282   if (ParseMaterial(status)) {
283     return true;
284   }
285   if (ParseMaterialLib(status)) {
286     return true;
287   }
288   if (ParseObject(status)) {
289     return true;
290   }
291   // No known definition was found. Ignore the line.
292   parser::SkipLine(buffer());
293   return true;
294 }
295 
ParseVertexPosition(Status * status)296 bool ObjDecoder::ParseVertexPosition(Status *status) {
297   std::array<char, 2> c;
298   if (!buffer()->Peek(&c)) {
299     return false;
300   }
301   if (c[0] != 'v' || c[1] != ' ') {
302     return false;
303   }
304   // Vertex definition found!
305   buffer()->Advance(2);
306   if (!counting_mode_) {
307     // Parse three float numbers for vertex position coordinates.
308     float val[3];
309     for (int i = 0; i < 3; ++i) {
310       parser::SkipWhitespace(buffer());
311       if (!parser::ParseFloat(buffer(), val + i)) {
312         *status = Status(Status::DRACO_ERROR, "Failed to parse a float number");
313         // The definition is processed so return true.
314         return true;
315       }
316     }
317     out_point_cloud_->attribute(pos_att_id_)
318         ->SetAttributeValue(AttributeValueIndex(num_positions_), val);
319   }
320   ++num_positions_;
321   parser::SkipLine(buffer());
322   return true;
323 }
324 
ParseNormal(Status * status)325 bool ObjDecoder::ParseNormal(Status *status) {
326   std::array<char, 2> c;
327   if (!buffer()->Peek(&c)) {
328     return false;
329   }
330   if (c[0] != 'v' || c[1] != 'n') {
331     return false;
332   }
333   // Normal definition found!
334   buffer()->Advance(2);
335   if (!counting_mode_) {
336     // Parse three float numbers for the normal vector.
337     float val[3];
338     for (int i = 0; i < 3; ++i) {
339       parser::SkipWhitespace(buffer());
340       if (!parser::ParseFloat(buffer(), val + i)) {
341         *status = Status(Status::DRACO_ERROR, "Failed to parse a float number");
342         // The definition is processed so return true.
343         return true;
344       }
345     }
346     out_point_cloud_->attribute(norm_att_id_)
347         ->SetAttributeValue(AttributeValueIndex(num_normals_), val);
348   }
349   ++num_normals_;
350   parser::SkipLine(buffer());
351   return true;
352 }
353 
ParseTexCoord(Status * status)354 bool ObjDecoder::ParseTexCoord(Status *status) {
355   std::array<char, 2> c;
356   if (!buffer()->Peek(&c)) {
357     return false;
358   }
359   if (c[0] != 'v' || c[1] != 't') {
360     return false;
361   }
362   // Texture coord definition found!
363   buffer()->Advance(2);
364   if (!counting_mode_) {
365     // Parse two float numbers for the texture coordinate.
366     float val[2];
367     for (int i = 0; i < 2; ++i) {
368       parser::SkipWhitespace(buffer());
369       if (!parser::ParseFloat(buffer(), val + i)) {
370         *status = Status(Status::DRACO_ERROR, "Failed to parse a float number");
371         // The definition is processed so return true.
372         return true;
373       }
374     }
375     out_point_cloud_->attribute(tex_att_id_)
376         ->SetAttributeValue(AttributeValueIndex(num_tex_coords_), val);
377   }
378   ++num_tex_coords_;
379   parser::SkipLine(buffer());
380   return true;
381 }
382 
ParseFace(Status * status)383 bool ObjDecoder::ParseFace(Status *status) {
384   char c;
385   if (!buffer()->Peek(&c)) {
386     return false;
387   }
388   if (c != 'f') {
389     return false;
390   }
391   // Face definition found!
392   buffer()->Advance(1);
393   if (!counting_mode_) {
394     std::array<int32_t, 3> indices[4];
395     // Parse face indices (we try to look for up to four to support quads).
396     int num_valid_indices = 0;
397     for (int i = 0; i < 4; ++i) {
398       if (!ParseVertexIndices(&indices[i])) {
399         if (i == 3) {
400           break;  // It's OK if there is no fourth vertex index.
401         }
402         *status = Status(Status::DRACO_ERROR, "Failed to parse vertex indices");
403         return true;
404       }
405       ++num_valid_indices;
406     }
407     // Process the first face.
408     for (int i = 0; i < 3; ++i) {
409       const PointIndex vert_id(3 * num_obj_faces_ + i);
410       MapPointToVertexIndices(vert_id, indices[i]);
411     }
412     ++num_obj_faces_;
413     if (num_valid_indices == 4) {
414       // Add an additional triangle for the quad.
415       //
416       //   3----2
417       //   |  / |
418       //   | /  |
419       //   0----1
420       //
421       const PointIndex vert_id(3 * num_obj_faces_);
422       MapPointToVertexIndices(vert_id, indices[0]);
423       MapPointToVertexIndices(vert_id + 1, indices[2]);
424       MapPointToVertexIndices(vert_id + 2, indices[3]);
425       ++num_obj_faces_;
426     }
427   } else {
428     // We are in the counting mode.
429     // We need to determine how many triangles are in the obj face.
430     // Go over the line and check how many gaps there are between non-empty
431     // sub-strings.
432     parser::SkipWhitespace(buffer());
433     int num_indices = 0;
434     bool is_end = false;
435     while (buffer()->Peek(&c) && c != '\n') {
436       if (parser::PeekWhitespace(buffer(), &is_end)) {
437         buffer()->Advance(1);
438       } else {
439         // Non-whitespace reached.. assume it's index declaration, skip it.
440         num_indices++;
441         while (!parser::PeekWhitespace(buffer(), &is_end) && !is_end) {
442           buffer()->Advance(1);
443         }
444       }
445     }
446     if (num_indices < 3 || num_indices > 4) {
447       *status =
448           Status(Status::DRACO_ERROR, "Invalid number of indices on a face");
449       return false;
450     }
451     // Either one or two new triangles.
452     num_obj_faces_ += num_indices - 2;
453   }
454   parser::SkipLine(buffer());
455   return true;
456 }
457 
ParseMaterialLib(Status * status)458 bool ObjDecoder::ParseMaterialLib(Status *status) {
459   // Allow only one material library per file for now.
460   if (!material_name_to_id_.empty()) {
461     return false;
462   }
463   std::array<char, 6> c;
464   if (!buffer()->Peek(&c)) {
465     return false;
466   }
467   if (std::memcmp(&c[0], "mtllib", 6) != 0) {
468     return false;
469   }
470   buffer()->Advance(6);
471   DecoderBuffer line_buffer = parser::ParseLineIntoDecoderBuffer(buffer());
472   parser::SkipWhitespace(&line_buffer);
473   material_file_name_.clear();
474   if (!parser::ParseString(&line_buffer, &material_file_name_)) {
475     *status = Status(Status::DRACO_ERROR, "Failed to parse material file name");
476     return true;
477   }
478   parser::SkipLine(&line_buffer);
479 
480   if (!material_file_name_.empty()) {
481     if (!ParseMaterialFile(material_file_name_, status)) {
482       // Silently ignore problems with material files for now.
483       return true;
484     }
485   }
486   return true;
487 }
488 
ParseMaterial(Status *)489 bool ObjDecoder::ParseMaterial(Status * /* status */) {
490   // In second pass, skip when we don't use materials.
491   if (!counting_mode_ && material_att_id_ < 0) {
492     return false;
493   }
494   std::array<char, 6> c;
495   if (!buffer()->Peek(&c)) {
496     return false;
497   }
498   if (std::memcmp(&c[0], "usemtl", 6) != 0) {
499     return false;
500   }
501   buffer()->Advance(6);
502   DecoderBuffer line_buffer = parser::ParseLineIntoDecoderBuffer(buffer());
503   parser::SkipWhitespace(&line_buffer);
504   std::string mat_name;
505   parser::ParseLine(&line_buffer, &mat_name);
506   if (mat_name.length() == 0) {
507     return false;
508   }
509   auto it = material_name_to_id_.find(mat_name);
510   if (it == material_name_to_id_.end()) {
511     // In first pass, materials found in obj that's not in the .mtl file
512     // will be added to the list.
513     last_material_id_ = num_materials_;
514     material_name_to_id_[mat_name] = num_materials_++;
515 
516     return true;
517   }
518   last_material_id_ = it->second;
519   return true;
520 }
521 
ParseObject(Status * status)522 bool ObjDecoder::ParseObject(Status *status) {
523   std::array<char, 2> c;
524   if (!buffer()->Peek(&c)) {
525     return false;
526   }
527   if (std::memcmp(&c[0], "o ", 2) != 0) {
528     return false;
529   }
530   buffer()->Advance(1);
531   DecoderBuffer line_buffer = parser::ParseLineIntoDecoderBuffer(buffer());
532   parser::SkipWhitespace(&line_buffer);
533   std::string obj_name;
534   if (!parser::ParseString(&line_buffer, &obj_name)) {
535     return false;
536   }
537   if (obj_name.length() == 0) {
538     return true;  // Ignore empty name entries.
539   }
540   auto it = obj_name_to_id_.find(obj_name);
541   if (it == obj_name_to_id_.end()) {
542     const int num_obj = static_cast<int>(obj_name_to_id_.size());
543     obj_name_to_id_[obj_name] = num_obj;
544     last_sub_obj_id_ = num_obj;
545   } else {
546     last_sub_obj_id_ = it->second;
547   }
548   return true;
549 }
550 
ParseVertexIndices(std::array<int32_t,3> * out_indices)551 bool ObjDecoder::ParseVertexIndices(std::array<int32_t, 3> *out_indices) {
552   // Parsed attribute indices can be in format:
553   // 1. POS_INDEX
554   // 2. POS_INDEX/TEX_COORD_INDEX
555   // 3. POS_INDEX/TEX_COORD_INDEX/NORMAL_INDEX
556   // 4. POS_INDEX//NORMAL_INDEX
557   parser::SkipCharacters(buffer(), " \t");
558   if (!parser::ParseSignedInt(buffer(), &(*out_indices)[0]) ||
559       (*out_indices)[0] == 0) {
560     return false;  // Position index must be present and valid.
561   }
562   (*out_indices)[1] = (*out_indices)[2] = 0;
563   char ch;
564   if (!buffer()->Peek(&ch)) {
565     return true;  // It may be OK if we cannot read any more characters.
566   }
567   if (ch != '/') {
568     return true;
569   }
570   buffer()->Advance(1);
571   // Check if we should skip texture index or not.
572   if (!buffer()->Peek(&ch)) {
573     return false;  // Here, we should be always able to read the next char.
574   }
575   if (ch != '/') {
576     // Must be texture coord index.
577     if (!parser::ParseSignedInt(buffer(), &(*out_indices)[1]) ||
578         (*out_indices)[1] == 0) {
579       return false;  // Texture index must be present and valid.
580     }
581   }
582   if (!buffer()->Peek(&ch)) {
583     return true;
584   }
585   if (ch == '/') {
586     buffer()->Advance(1);
587     // Read normal index.
588     if (!parser::ParseSignedInt(buffer(), &(*out_indices)[2]) ||
589         (*out_indices)[2] == 0) {
590       return false;  // Normal index must be present and valid.
591     }
592   }
593   return true;
594 }
595 
MapPointToVertexIndices(PointIndex vert_id,const std::array<int32_t,3> & indices)596 void ObjDecoder::MapPointToVertexIndices(
597     PointIndex vert_id, const std::array<int32_t, 3> &indices) {
598   // Use face entries to store mapping between vertex and attribute indices
599   // (positions, texture coordinates and normal indices).
600   // Any given index is used when indices[x] != 0. For positive values, the
601   // point is mapped directly to the specified attribute index. Negative input
602   // indices indicate addressing from the last element (e.g. -1 is the last
603   // attribute value of a given type, -2 the second last, etc.).
604   if (indices[0] > 0) {
605     out_point_cloud_->attribute(pos_att_id_)
606         ->SetPointMapEntry(vert_id, AttributeValueIndex(indices[0] - 1));
607   } else if (indices[0] < 0) {
608     out_point_cloud_->attribute(pos_att_id_)
609         ->SetPointMapEntry(vert_id,
610                            AttributeValueIndex(num_positions_ + indices[0]));
611   }
612 
613   if (tex_att_id_ >= 0) {
614     if (indices[1] > 0) {
615       out_point_cloud_->attribute(tex_att_id_)
616           ->SetPointMapEntry(vert_id, AttributeValueIndex(indices[1] - 1));
617     } else if (indices[1] < 0) {
618       out_point_cloud_->attribute(tex_att_id_)
619           ->SetPointMapEntry(vert_id,
620                              AttributeValueIndex(num_tex_coords_ + indices[1]));
621     } else {
622       // Texture index not provided but expected. Insert 0 entry as the
623       // default value.
624       out_point_cloud_->attribute(tex_att_id_)
625           ->SetPointMapEntry(vert_id, AttributeValueIndex(0));
626     }
627   }
628 
629   if (norm_att_id_ >= 0) {
630     if (indices[2] > 0) {
631       out_point_cloud_->attribute(norm_att_id_)
632           ->SetPointMapEntry(vert_id, AttributeValueIndex(indices[2] - 1));
633     } else if (indices[2] < 0) {
634       out_point_cloud_->attribute(norm_att_id_)
635           ->SetPointMapEntry(vert_id,
636                              AttributeValueIndex(num_normals_ + indices[2]));
637     } else {
638       // Normal index not provided but expected. Insert 0 entry as the default
639       // value.
640       out_point_cloud_->attribute(norm_att_id_)
641           ->SetPointMapEntry(vert_id, AttributeValueIndex(0));
642     }
643   }
644 
645   // Assign material index to the point if it is available.
646   if (material_att_id_ >= 0) {
647     out_point_cloud_->attribute(material_att_id_)
648         ->SetPointMapEntry(vert_id, AttributeValueIndex(last_material_id_));
649   }
650 
651   // Assign sub-object index to the point if it is available.
652   if (sub_obj_att_id_ >= 0) {
653     out_point_cloud_->attribute(sub_obj_att_id_)
654         ->SetPointMapEntry(vert_id, AttributeValueIndex(last_sub_obj_id_));
655   }
656 }
657 
ParseMaterialFile(const std::string & file_name,Status * status)658 bool ObjDecoder::ParseMaterialFile(const std::string &file_name,
659                                    Status *status) {
660   const std::string full_path = GetFullPath(file_name, input_file_name_);
661   std::vector<char> buffer;
662   if (!ReadFileToBuffer(full_path, &buffer)) {
663     return false;
664   }
665 
666   // Backup the original decoder buffer.
667   DecoderBuffer old_buffer = buffer_;
668 
669   buffer_.Init(buffer.data(), buffer.size());
670 
671   num_materials_ = 0;
672   while (ParseMaterialFileDefinition(status)) {
673   }
674 
675   // Restore the original buffer.
676   buffer_ = old_buffer;
677   return true;
678 }
679 
ParseMaterialFileDefinition(Status *)680 bool ObjDecoder::ParseMaterialFileDefinition(Status * /* status */) {
681   char c;
682   parser::SkipWhitespace(buffer());
683   if (!buffer()->Peek(&c)) {
684     // End of file reached?.
685     return false;
686   }
687   if (c == '#') {
688     // Comment, ignore the line.
689     parser::SkipLine(buffer());
690     return true;
691   }
692   std::string str;
693   if (!parser::ParseString(buffer(), &str)) {
694     return false;
695   }
696   if (str == "newmtl") {
697     parser::SkipWhitespace(buffer());
698     parser::ParseLine(buffer(), &str);
699     if (str.empty()) {
700       return false;
701     }
702     // Add new material to our map.
703     material_name_to_id_[str] = num_materials_++;
704   }
705   return true;
706 }
707 
708 }  // namespace draco
709