Changeset View
Changeset View
Standalone View
Standalone View
extern/draco/dracoenc/src/draco/io/obj_decoder.cc
- This file was added.
| // Copyright 2016 The Draco Authors. | |||||
| // | |||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| // you may not use this file except in compliance with the License. | |||||
| // You may obtain a copy of the License at | |||||
| // | |||||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||||
| // | |||||
| // Unless required by applicable law or agreed to in writing, software | |||||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| // See the License for the specific language governing permissions and | |||||
| // limitations under the License. | |||||
| // | |||||
| #include "draco/io/obj_decoder.h" | |||||
| #include <cctype> | |||||
| #include <cmath> | |||||
| #include <fstream> | |||||
| #include "draco/io/parser_utils.h" | |||||
| #include "draco/metadata/geometry_metadata.h" | |||||
| namespace draco { | |||||
| ObjDecoder::ObjDecoder() | |||||
| : counting_mode_(true), | |||||
| num_obj_faces_(0), | |||||
| num_positions_(0), | |||||
| num_tex_coords_(0), | |||||
| num_normals_(0), | |||||
| num_materials_(0), | |||||
| last_sub_obj_id_(0), | |||||
| pos_att_id_(-1), | |||||
| tex_att_id_(-1), | |||||
| norm_att_id_(-1), | |||||
| material_att_id_(-1), | |||||
| sub_obj_att_id_(-1), | |||||
| deduplicate_input_values_(true), | |||||
| last_material_id_(0), | |||||
| use_metadata_(false), | |||||
| out_mesh_(nullptr), | |||||
| out_point_cloud_(nullptr) {} | |||||
| Status ObjDecoder::DecodeFromFile(const std::string &file_name, | |||||
| Mesh *out_mesh) { | |||||
| out_mesh_ = out_mesh; | |||||
| return DecodeFromFile(file_name, static_cast<PointCloud *>(out_mesh)); | |||||
| } | |||||
| Status ObjDecoder::DecodeFromFile(const std::string &file_name, | |||||
| PointCloud *out_point_cloud) { | |||||
| std::ifstream file(file_name, std::ios::binary); | |||||
| if (!file) | |||||
| return Status(Status::IO_ERROR); | |||||
| // Read the whole file into a buffer. | |||||
| auto pos0 = file.tellg(); | |||||
| file.seekg(0, std::ios::end); | |||||
| auto file_size = file.tellg() - pos0; | |||||
| if (file_size == 0) | |||||
| return Status(Status::IO_ERROR); | |||||
| file.seekg(0, std::ios::beg); | |||||
| std::vector<char> data(file_size); | |||||
| file.read(&data[0], file_size); | |||||
| buffer_.Init(&data[0], file_size); | |||||
| out_point_cloud_ = out_point_cloud; | |||||
| input_file_name_ = file_name; | |||||
| return DecodeInternal(); | |||||
| } | |||||
| Status ObjDecoder::DecodeFromBuffer(DecoderBuffer *buffer, Mesh *out_mesh) { | |||||
| out_mesh_ = out_mesh; | |||||
| return DecodeFromBuffer(buffer, static_cast<PointCloud *>(out_mesh)); | |||||
| } | |||||
| Status ObjDecoder::DecodeFromBuffer(DecoderBuffer *buffer, | |||||
| PointCloud *out_point_cloud) { | |||||
| out_point_cloud_ = out_point_cloud; | |||||
| buffer_.Init(buffer->data_head(), buffer->remaining_size()); | |||||
| return DecodeInternal(); | |||||
| } | |||||
| Status ObjDecoder::DecodeInternal() { | |||||
| // In the first pass, count the number of different elements in the geometry. | |||||
| // In case the desired output is just a point cloud (i.e., when | |||||
| // out_mesh_ == nullptr) the decoder will ignore all information about the | |||||
| // connectivity that may be included in the source data. | |||||
| counting_mode_ = true; | |||||
| ResetCounters(); | |||||
| material_name_to_id_.clear(); | |||||
| last_sub_obj_id_ = 0; | |||||
| // Parse all lines. | |||||
| Status status(Status::OK); | |||||
| while (ParseDefinition(&status) && status.ok()) { | |||||
| } | |||||
| if (!status.ok()) | |||||
| return status; | |||||
| bool use_identity_mapping = false; | |||||
| if (num_obj_faces_ == 0) { | |||||
| // Mesh has no faces. In this case we try to read the geometry as a point | |||||
| // cloud where every attribute entry is a point. | |||||
| // Ensure the number of all entries is same for all attributes. | |||||
| if (num_positions_ == 0) | |||||
| return Status(Status::ERROR, "No position attribute"); | |||||
| if (num_tex_coords_ > 0 && num_tex_coords_ != num_positions_) | |||||
| return Status(Status::ERROR, | |||||
| "Invalid number of texture coordinates for a point cloud"); | |||||
| if (num_normals_ > 0 && num_normals_ != num_positions_) | |||||
| return Status(Status::ERROR, | |||||
| "Invalid number of normals for a point cloud"); | |||||
| out_mesh_ = nullptr; // Treat the output geometry as a point cloud. | |||||
| use_identity_mapping = true; | |||||
| } | |||||
| // Initialize point cloud and mesh properties. | |||||
| if (out_mesh_) { | |||||
| // Start decoding a mesh with the given number of faces. For point clouds we | |||||
| // silently ignore all data about the mesh connectivity. | |||||
| out_mesh_->SetNumFaces(num_obj_faces_); | |||||
| } | |||||
| if (num_obj_faces_ > 0) { | |||||
| out_point_cloud_->set_num_points(3 * num_obj_faces_); | |||||
| } else { | |||||
| out_point_cloud_->set_num_points(num_positions_); | |||||
| } | |||||
| // Add attributes if they are present in the input data. | |||||
| if (num_positions_ > 0) { | |||||
| GeometryAttribute va; | |||||
| va.Init(GeometryAttribute::POSITION, nullptr, 3, DT_FLOAT32, false, | |||||
| sizeof(float) * 3, 0); | |||||
| pos_att_id_ = out_point_cloud_->AddAttribute(va, use_identity_mapping, | |||||
| num_positions_); | |||||
| } | |||||
| if (num_tex_coords_ > 0) { | |||||
| GeometryAttribute va; | |||||
| va.Init(GeometryAttribute::TEX_COORD, nullptr, 2, DT_FLOAT32, false, | |||||
| sizeof(float) * 2, 0); | |||||
| tex_att_id_ = out_point_cloud_->AddAttribute(va, use_identity_mapping, | |||||
| num_tex_coords_); | |||||
| } | |||||
| if (num_normals_ > 0) { | |||||
| GeometryAttribute va; | |||||
| va.Init(GeometryAttribute::NORMAL, nullptr, 3, DT_FLOAT32, false, | |||||
| sizeof(float) * 3, 0); | |||||
| norm_att_id_ = | |||||
| out_point_cloud_->AddAttribute(va, use_identity_mapping, num_normals_); | |||||
| } | |||||
| if (num_materials_ > 0 && num_obj_faces_ > 0) { | |||||
| GeometryAttribute va; | |||||
| if (num_materials_ < 256) { | |||||
| va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT8, false, 1, 0); | |||||
| } else if (num_materials_ < (1 << 16)) { | |||||
| va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT16, false, 2, 0); | |||||
| } else { | |||||
| va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT32, false, 4, 0); | |||||
| } | |||||
| material_att_id_ = | |||||
| out_point_cloud_->AddAttribute(va, false, num_materials_); | |||||
| // Fill the material entries. | |||||
| for (int i = 0; i < num_materials_; ++i) { | |||||
| const AttributeValueIndex avi(i); | |||||
| out_point_cloud_->attribute(material_att_id_)->SetAttributeValue(avi, &i); | |||||
| } | |||||
| if (use_metadata_) { | |||||
| // Use metadata to store the name of materials. | |||||
| std::unique_ptr<AttributeMetadata> material_metadata = | |||||
| std::unique_ptr<AttributeMetadata>(new AttributeMetadata()); | |||||
| material_metadata->AddEntryString("name", "material"); | |||||
| // Add all material names. | |||||
| for (const auto &itr : material_name_to_id_) { | |||||
| material_metadata->AddEntryInt(itr.first, itr.second); | |||||
| } | |||||
| if (!material_file_name_.empty()) { | |||||
| material_metadata->AddEntryString("file_name", material_file_name_); | |||||
| } | |||||
| out_point_cloud_->AddAttributeMetadata(material_att_id_, | |||||
| std::move(material_metadata)); | |||||
| } | |||||
| } | |||||
| if (!obj_name_to_id_.empty() && num_obj_faces_ > 0) { | |||||
| GeometryAttribute va; | |||||
| if (obj_name_to_id_.size() < 256) { | |||||
| va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT8, false, 1, 0); | |||||
| } else if (obj_name_to_id_.size() < (1 << 16)) { | |||||
| va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT16, false, 2, 0); | |||||
| } else { | |||||
| va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT32, false, 4, 0); | |||||
| } | |||||
| sub_obj_att_id_ = out_point_cloud_->AddAttribute( | |||||
| va, false, static_cast<uint32_t>(obj_name_to_id_.size())); | |||||
| // Fill the sub object id entries. | |||||
| for (const auto &itr : obj_name_to_id_) { | |||||
| const AttributeValueIndex i(itr.second); | |||||
| out_point_cloud_->attribute(sub_obj_att_id_)->SetAttributeValue(i, &i); | |||||
| } | |||||
| if (use_metadata_) { | |||||
| // Use metadata to store the name of materials. | |||||
| std::unique_ptr<AttributeMetadata> sub_obj_metadata = | |||||
| std::unique_ptr<AttributeMetadata>(new AttributeMetadata()); | |||||
| sub_obj_metadata->AddEntryString("name", "sub_obj"); | |||||
| // Add all sub object names. | |||||
| for (const auto &itr : obj_name_to_id_) { | |||||
| const AttributeValueIndex i(itr.second); | |||||
| sub_obj_metadata->AddEntryInt(itr.first, itr.second); | |||||
| } | |||||
| out_point_cloud_->AddAttributeMetadata(sub_obj_att_id_, | |||||
| std::move(sub_obj_metadata)); | |||||
| } | |||||
| } | |||||
| // Perform a second iteration of parsing and fill all the data. | |||||
| counting_mode_ = false; | |||||
| ResetCounters(); | |||||
| // Start parsing from the beginning of the buffer again. | |||||
| buffer()->StartDecodingFrom(0); | |||||
| while (ParseDefinition(&status) && status.ok()) { | |||||
| } | |||||
| if (!status.ok()) | |||||
| return status; | |||||
| if (out_mesh_) { | |||||
| // Add faces with identity mapping between vertex and corner indices. | |||||
| // Duplicate vertices will get removed later. | |||||
| Mesh::Face face; | |||||
| for (FaceIndex i(0); i < num_obj_faces_; ++i) { | |||||
| for (int c = 0; c < 3; ++c) | |||||
| face[c] = 3 * i.value() + c; | |||||
| out_mesh_->SetFace(i, face); | |||||
| } | |||||
| } | |||||
| #ifdef DRACO_ATTRIBUTE_DEDUPLICATION_SUPPORTED | |||||
| if (deduplicate_input_values_) { | |||||
| out_point_cloud_->DeduplicateAttributeValues(); | |||||
| } | |||||
| out_point_cloud_->DeduplicatePointIds(); | |||||
| #endif | |||||
| return status; | |||||
| } | |||||
| void ObjDecoder::ResetCounters() { | |||||
| num_obj_faces_ = 0; | |||||
| num_positions_ = 0; | |||||
| num_tex_coords_ = 0; | |||||
| num_normals_ = 0; | |||||
| last_material_id_ = 0; | |||||
| last_sub_obj_id_ = 0; | |||||
| } | |||||
| bool ObjDecoder::ParseDefinition(Status *status) { | |||||
| char c; | |||||
| parser::SkipWhitespace(buffer()); | |||||
| if (!buffer()->Peek(&c)) { | |||||
| // End of file reached?. | |||||
| return false; | |||||
| } | |||||
| if (c == '#') { | |||||
| // Comment, ignore the line. | |||||
| parser::SkipLine(buffer()); | |||||
| return true; | |||||
| } | |||||
| if (ParseVertexPosition(status)) | |||||
| return true; | |||||
| if (ParseNormal(status)) | |||||
| return true; | |||||
| if (ParseTexCoord(status)) | |||||
| return true; | |||||
| if (ParseFace(status)) | |||||
| return true; | |||||
| if (ParseMaterial(status)) | |||||
| return true; | |||||
| if (ParseMaterialLib(status)) | |||||
| return true; | |||||
| if (ParseObject(status)) | |||||
| return true; | |||||
| // No known definition was found. Ignore the line. | |||||
| parser::SkipLine(buffer()); | |||||
| return true; | |||||
| } | |||||
| bool ObjDecoder::ParseVertexPosition(Status *status) { | |||||
| std::array<char, 2> c; | |||||
| if (!buffer()->Peek(&c)) { | |||||
| return false; | |||||
| } | |||||
| if (c[0] != 'v' || c[1] != ' ') | |||||
| return false; | |||||
| // Vertex definition found! | |||||
| buffer()->Advance(2); | |||||
| if (!counting_mode_) { | |||||
| // Parse three float numbers for vertex position coordinates. | |||||
| float val[3]; | |||||
| for (int i = 0; i < 3; ++i) { | |||||
| parser::SkipWhitespace(buffer()); | |||||
| if (!parser::ParseFloat(buffer(), val + i)) { | |||||
| *status = Status(Status::ERROR, "Failed to parse a float number"); | |||||
| // The definition is processed so return true. | |||||
| return true; | |||||
| } | |||||
| } | |||||
| out_point_cloud_->attribute(pos_att_id_) | |||||
| ->SetAttributeValue(AttributeValueIndex(num_positions_), val); | |||||
| } | |||||
| ++num_positions_; | |||||
| parser::SkipLine(buffer()); | |||||
| return true; | |||||
| } | |||||
| bool ObjDecoder::ParseNormal(Status *status) { | |||||
| std::array<char, 2> c; | |||||
| if (!buffer()->Peek(&c)) { | |||||
| return false; | |||||
| } | |||||
| if (c[0] != 'v' || c[1] != 'n') | |||||
| return false; | |||||
| // Normal definition found! | |||||
| buffer()->Advance(2); | |||||
| if (!counting_mode_) { | |||||
| // Parse three float numbers for the normal vector. | |||||
| float val[3]; | |||||
| for (int i = 0; i < 3; ++i) { | |||||
| parser::SkipWhitespace(buffer()); | |||||
| if (!parser::ParseFloat(buffer(), val + i)) { | |||||
| *status = Status(Status::ERROR, "Failed to parse a float number"); | |||||
| // The definition is processed so return true. | |||||
| return true; | |||||
| } | |||||
| } | |||||
| out_point_cloud_->attribute(norm_att_id_) | |||||
| ->SetAttributeValue(AttributeValueIndex(num_normals_), val); | |||||
| } | |||||
| ++num_normals_; | |||||
| parser::SkipLine(buffer()); | |||||
| return true; | |||||
| } | |||||
| bool ObjDecoder::ParseTexCoord(Status *status) { | |||||
| std::array<char, 2> c; | |||||
| if (!buffer()->Peek(&c)) { | |||||
| return false; | |||||
| } | |||||
| if (c[0] != 'v' || c[1] != 't') | |||||
| return false; | |||||
| // Texture coord definition found! | |||||
| buffer()->Advance(2); | |||||
| if (!counting_mode_) { | |||||
| // Parse two float numbers for the texture coordinate. | |||||
| float val[2]; | |||||
| for (int i = 0; i < 2; ++i) { | |||||
| parser::SkipWhitespace(buffer()); | |||||
| if (!parser::ParseFloat(buffer(), val + i)) { | |||||
| *status = Status(Status::ERROR, "Failed to parse a float number"); | |||||
| // The definition is processed so return true. | |||||
| return true; | |||||
| } | |||||
| } | |||||
| out_point_cloud_->attribute(tex_att_id_) | |||||
| ->SetAttributeValue(AttributeValueIndex(num_tex_coords_), val); | |||||
| } | |||||
| ++num_tex_coords_; | |||||
| parser::SkipLine(buffer()); | |||||
| return true; | |||||
| } | |||||
| bool ObjDecoder::ParseFace(Status *status) { | |||||
| char c; | |||||
| if (!buffer()->Peek(&c)) { | |||||
| return false; | |||||
| } | |||||
| if (c != 'f') | |||||
| return false; | |||||
| // Face definition found! | |||||
| buffer()->Advance(1); | |||||
| if (!counting_mode_) { | |||||
| std::array<int32_t, 3> indices[4]; | |||||
| // Parse face indices (we try to look for up to four to support quads). | |||||
| int num_valid_indices = 0; | |||||
| for (int i = 0; i < 4; ++i) { | |||||
| if (!ParseVertexIndices(&indices[i])) { | |||||
| if (i == 3) { | |||||
| break; // It's OK if there is no fourth vertex index. | |||||
| } | |||||
| *status = Status(Status::ERROR, "Failed to parse vertex indices"); | |||||
| return true; | |||||
| } | |||||
| ++num_valid_indices; | |||||
| } | |||||
| // Process the first face. | |||||
| for (int i = 0; i < 3; ++i) { | |||||
| const PointIndex vert_id(3 * num_obj_faces_ + i); | |||||
| MapPointToVertexIndices(vert_id, indices[i]); | |||||
| } | |||||
| ++num_obj_faces_; | |||||
| if (num_valid_indices == 4) { | |||||
| // Add an additional triangle for the quad. | |||||
| // | |||||
| // 3----2 | |||||
| // | / | | |||||
| // | / | | |||||
| // 0----1 | |||||
| // | |||||
| const PointIndex vert_id(3 * num_obj_faces_); | |||||
| MapPointToVertexIndices(vert_id, indices[0]); | |||||
| MapPointToVertexIndices(vert_id + 1, indices[2]); | |||||
| MapPointToVertexIndices(vert_id + 2, indices[3]); | |||||
| ++num_obj_faces_; | |||||
| } | |||||
| } else { | |||||
| // We are in the counting mode. | |||||
| // We need to determine how many triangles are in the obj face. | |||||
| // Go over the line and check how many gaps there are between non-empty | |||||
| // sub-strings. | |||||
| parser::SkipWhitespace(buffer()); | |||||
| int num_indices = 0; | |||||
| bool is_end = false; | |||||
| while (buffer()->Peek(&c) && c != '\n') { | |||||
| if (parser::PeekWhitespace(buffer(), &is_end)) { | |||||
| buffer()->Advance(1); | |||||
| } else { | |||||
| // Non-whitespace reached.. assume it's index declaration, skip it. | |||||
| num_indices++; | |||||
| while (!parser::PeekWhitespace(buffer(), &is_end) && !is_end) { | |||||
| buffer()->Advance(1); | |||||
| } | |||||
| } | |||||
| } | |||||
| if (num_indices < 3 || num_indices > 4) { | |||||
| *status = Status(Status::ERROR, "Invalid number of indices on a face"); | |||||
| return false; | |||||
| } | |||||
| // Either one or two new triangles. | |||||
| num_obj_faces_ += num_indices - 2; | |||||
| } | |||||
| parser::SkipLine(buffer()); | |||||
| return true; | |||||
| } | |||||
| bool ObjDecoder::ParseMaterialLib(Status *status) { | |||||
| // Allow only one material library per file for now. | |||||
| if (material_name_to_id_.size() > 0) | |||||
| return false; | |||||
| std::array<char, 6> c; | |||||
| if (!buffer()->Peek(&c)) { | |||||
| return false; | |||||
| } | |||||
| if (std::memcmp(&c[0], "mtllib", 6) != 0) | |||||
| return false; | |||||
| buffer()->Advance(6); | |||||
| DecoderBuffer line_buffer = parser::ParseLineIntoDecoderBuffer(buffer()); | |||||
| parser::SkipWhitespace(&line_buffer); | |||||
| material_file_name_.clear(); | |||||
| if (!parser::ParseString(&line_buffer, &material_file_name_)) { | |||||
| *status = Status(Status::ERROR, "Failed to parse material file name"); | |||||
| return true; | |||||
| } | |||||
| parser::SkipLine(&line_buffer); | |||||
| if (material_file_name_.size() > 0) { | |||||
| if (!ParseMaterialFile(material_file_name_, status)) { | |||||
| // Silently ignore problems with material files for now. | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| bool ObjDecoder::ParseMaterial(Status * /* status */) { | |||||
| // In second pass, skip when we don't use materials. | |||||
| if (!counting_mode_ && material_att_id_ < 0) | |||||
| return false; | |||||
| std::array<char, 6> c; | |||||
| if (!buffer()->Peek(&c)) { | |||||
| return false; | |||||
| } | |||||
| if (std::memcmp(&c[0], "usemtl", 6) != 0) | |||||
| return false; | |||||
| buffer()->Advance(6); | |||||
| DecoderBuffer line_buffer = parser::ParseLineIntoDecoderBuffer(buffer()); | |||||
| parser::SkipWhitespace(&line_buffer); | |||||
| std::string mat_name; | |||||
| parser::ParseLine(&line_buffer, &mat_name); | |||||
| if (mat_name.length() == 0) | |||||
| return false; | |||||
| auto it = material_name_to_id_.find(mat_name); | |||||
| if (it == material_name_to_id_.end()) { | |||||
| // In first pass, materials found in obj that's not in the .mtl file | |||||
| // will be added to the list. | |||||
| last_material_id_ = num_materials_; | |||||
| material_name_to_id_[mat_name] = num_materials_++; | |||||
| return true; | |||||
| } | |||||
| last_material_id_ = it->second; | |||||
| return true; | |||||
| } | |||||
| bool ObjDecoder::ParseObject(Status *status) { | |||||
| std::array<char, 2> c; | |||||
| if (!buffer()->Peek(&c)) { | |||||
| return false; | |||||
| } | |||||
| if (std::memcmp(&c[0], "o ", 2) != 0) | |||||
| return false; | |||||
| buffer()->Advance(1); | |||||
| DecoderBuffer line_buffer = parser::ParseLineIntoDecoderBuffer(buffer()); | |||||
| parser::SkipWhitespace(&line_buffer); | |||||
| std::string obj_name; | |||||
| if (!parser::ParseString(&line_buffer, &obj_name)) | |||||
| return false; | |||||
| if (obj_name.length() == 0) | |||||
| return true; // Ignore empty name entries. | |||||
| auto it = obj_name_to_id_.find(obj_name); | |||||
| if (it == obj_name_to_id_.end()) { | |||||
| const int num_obj = static_cast<int>(obj_name_to_id_.size()); | |||||
| obj_name_to_id_[obj_name] = num_obj; | |||||
| last_sub_obj_id_ = num_obj; | |||||
| } else { | |||||
| last_sub_obj_id_ = it->second; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| bool ObjDecoder::ParseVertexIndices(std::array<int32_t, 3> *out_indices) { | |||||
| // Parsed attribute indices can be in format: | |||||
| // 1. POS_INDEX | |||||
| // 2. POS_INDEX/TEX_COORD_INDEX | |||||
| // 3. POS_INDEX/TEX_COORD_INDEX/NORMAL_INDEX | |||||
| // 4. POS_INDEX//NORMAL_INDEX | |||||
| parser::SkipCharacters(buffer(), " \t"); | |||||
| if (!parser::ParseSignedInt(buffer(), &(*out_indices)[0]) || | |||||
| (*out_indices)[0] == 0) | |||||
| return false; // Position index must be present and valid. | |||||
| (*out_indices)[1] = (*out_indices)[2] = 0; | |||||
| char ch; | |||||
| if (!buffer()->Peek(&ch)) | |||||
| return true; // It may be OK if we cannot read any more characters. | |||||
| if (ch != '/') | |||||
| return true; | |||||
| buffer()->Advance(1); | |||||
| // Check if we should skip texture index or not. | |||||
| if (!buffer()->Peek(&ch)) | |||||
| return false; // Here, we should be always able to read the next char. | |||||
| if (ch != '/') { | |||||
| // Must be texture coord index. | |||||
| if (!parser::ParseSignedInt(buffer(), &(*out_indices)[1]) || | |||||
| (*out_indices)[1] == 0) | |||||
| return false; // Texture index must be present and valid. | |||||
| } | |||||
| if (!buffer()->Peek(&ch)) | |||||
| return true; | |||||
| if (ch == '/') { | |||||
| buffer()->Advance(1); | |||||
| // Read normal index. | |||||
| if (!parser::ParseSignedInt(buffer(), &(*out_indices)[2]) || | |||||
| (*out_indices)[2] == 0) | |||||
| return false; // Normal index must be present and valid. | |||||
| } | |||||
| return true; | |||||
| } | |||||
| void ObjDecoder::MapPointToVertexIndices( | |||||
| PointIndex vert_id, const std::array<int32_t, 3> &indices) { | |||||
| // Use face entries to store mapping between vertex and attribute indices | |||||
| // (positions, texture coordinates and normal indices). | |||||
| // Any given index is used when indices[x] != 0. For positive values, the | |||||
| // point is mapped directly to the specified attribute index. Negative input | |||||
| // indices indicate addressing from the last element (e.g. -1 is the last | |||||
| // attribute value of a given type, -2 the second last, etc.). | |||||
| if (indices[0] > 0) { | |||||
| out_point_cloud_->attribute(pos_att_id_) | |||||
| ->SetPointMapEntry(vert_id, AttributeValueIndex(indices[0] - 1)); | |||||
| } else if (indices[0] < 0) { | |||||
| out_point_cloud_->attribute(pos_att_id_) | |||||
| ->SetPointMapEntry(vert_id, | |||||
| AttributeValueIndex(num_positions_ + indices[0])); | |||||
| } | |||||
| if (tex_att_id_ >= 0) { | |||||
| if (indices[1] > 0) { | |||||
| out_point_cloud_->attribute(tex_att_id_) | |||||
| ->SetPointMapEntry(vert_id, AttributeValueIndex(indices[1] - 1)); | |||||
| } else if (indices[1] < 0) { | |||||
| out_point_cloud_->attribute(tex_att_id_) | |||||
| ->SetPointMapEntry(vert_id, | |||||
| AttributeValueIndex(num_tex_coords_ + indices[1])); | |||||
| } else { | |||||
| // Texture index not provided but expected. Insert 0 entry as the | |||||
| // default value. | |||||
| out_point_cloud_->attribute(tex_att_id_) | |||||
| ->SetPointMapEntry(vert_id, AttributeValueIndex(0)); | |||||
| } | |||||
| } | |||||
| if (norm_att_id_ >= 0) { | |||||
| if (indices[2] > 0) { | |||||
| out_point_cloud_->attribute(norm_att_id_) | |||||
| ->SetPointMapEntry(vert_id, AttributeValueIndex(indices[2] - 1)); | |||||
| } else if (indices[2] < 0) { | |||||
| out_point_cloud_->attribute(norm_att_id_) | |||||
| ->SetPointMapEntry(vert_id, | |||||
| AttributeValueIndex(num_normals_ + indices[2])); | |||||
| } else { | |||||
| // Normal index not provided but expected. Insert 0 entry as the default | |||||
| // value. | |||||
| out_point_cloud_->attribute(norm_att_id_) | |||||
| ->SetPointMapEntry(vert_id, AttributeValueIndex(0)); | |||||
| } | |||||
| } | |||||
| // Assign material index to the point if it is available. | |||||
| if (material_att_id_ >= 0) { | |||||
| out_point_cloud_->attribute(material_att_id_) | |||||
| ->SetPointMapEntry(vert_id, AttributeValueIndex(last_material_id_)); | |||||
| } | |||||
| // Assign sub-object index to the point if it is available. | |||||
| if (sub_obj_att_id_ >= 0) { | |||||
| out_point_cloud_->attribute(sub_obj_att_id_) | |||||
| ->SetPointMapEntry(vert_id, AttributeValueIndex(last_sub_obj_id_)); | |||||
| } | |||||
| } | |||||
| bool ObjDecoder::ParseMaterialFile(const std::string &file_name, | |||||
| Status *status) { | |||||
| // Get the correct path to the |file_name| using the folder from | |||||
| // |input_file_name_| as the root folder. | |||||
| const auto pos = input_file_name_.find_last_of("/\\"); | |||||
| std::string full_path; | |||||
| if (pos != std::string::npos) { | |||||
| full_path = input_file_name_.substr(0, pos + 1); | |||||
| } | |||||
| full_path += file_name; | |||||
| std::ifstream file(full_path, std::ios::binary); | |||||
| if (!file) | |||||
| return false; | |||||
| // Read the whole file into a buffer. | |||||
| file.seekg(0, std::ios::end); | |||||
| const std::string::size_type file_size = file.tellg(); | |||||
| if (file_size == 0) | |||||
| return false; | |||||
| file.seekg(0, std::ios::beg); | |||||
| std::vector<char> data(file_size); | |||||
| file.read(&data[0], file_size); | |||||
| // Backup the original decoder buffer. | |||||
| DecoderBuffer old_buffer = buffer_; | |||||
| buffer_.Init(&data[0], file_size); | |||||
| num_materials_ = 0; | |||||
| while (ParseMaterialFileDefinition(status)) { | |||||
| } | |||||
| // Restore the original buffer. | |||||
| buffer_ = old_buffer; | |||||
| return true; | |||||
| } | |||||
| bool ObjDecoder::ParseMaterialFileDefinition(Status * /* status */) { | |||||
| char c; | |||||
| parser::SkipWhitespace(buffer()); | |||||
| if (!buffer()->Peek(&c)) { | |||||
| // End of file reached?. | |||||
| return false; | |||||
| } | |||||
| if (c == '#') { | |||||
| // Comment, ignore the line. | |||||
| parser::SkipLine(buffer()); | |||||
| return true; | |||||
| } | |||||
| std::string str; | |||||
| if (!parser::ParseString(buffer(), &str)) | |||||
| return false; | |||||
| if (str.compare("newmtl") == 0) { | |||||
| parser::SkipWhitespace(buffer()); | |||||
| parser::ParseLine(buffer(), &str); | |||||
| if (str.length() == 0) | |||||
| return false; | |||||
| // Add new material to our map. | |||||
| material_name_to_id_[str] = num_materials_++; | |||||
| } | |||||
| parser::SkipLine(buffer()); | |||||
| return true; | |||||
| } | |||||
| } // namespace draco | |||||