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