diff options
Diffstat (limited to 'libs/assimp/contrib/draco/src/draco/io/obj_decoder.cc')
-rw-r--r-- | libs/assimp/contrib/draco/src/draco/io/obj_decoder.cc | 708 |
1 files changed, 708 insertions, 0 deletions
diff --git a/libs/assimp/contrib/draco/src/draco/io/obj_decoder.cc b/libs/assimp/contrib/draco/src/draco/io/obj_decoder.cc new file mode 100644 index 0000000..9b4eab6 --- /dev/null +++ b/libs/assimp/contrib/draco/src/draco/io/obj_decoder.cc @@ -0,0 +1,708 @@ +// 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 "draco/io/file_utils.h" +#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::vector<char> buffer; + if (!ReadFileToBuffer(file_name, &buffer)) { + return Status(Status::DRACO_ERROR, "Unable to read input file."); + } + buffer_.Init(buffer.data(), buffer.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::DRACO_ERROR, "No position attribute"); + } + if (num_tex_coords_ > 0 && num_tex_coords_ != num_positions_) { + return Status(Status::DRACO_ERROR, + "Invalid number of texture coordinates for a point cloud"); + } + if (num_normals_ > 0 && num_normals_ != num_positions_) { + return Status(Status::DRACO_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; + const auto geometry_attribute_type = GeometryAttribute::GENERIC; + if (num_materials_ < 256) { + va.Init(geometry_attribute_type, nullptr, 1, DT_UINT8, false, 1, 0); + } else if (num_materials_ < (1 << 16)) { + va.Init(geometry_attribute_type, nullptr, 1, DT_UINT16, false, 2, 0); + } else { + va.Init(geometry_attribute_type, 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_VALUES_DEDUPLICATION_SUPPORTED + if (deduplicate_input_values_) { + out_point_cloud_->DeduplicateAttributeValues(); + } +#endif +#ifdef DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED + 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::DRACO_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::DRACO_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::DRACO_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::DRACO_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::DRACO_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_.empty()) { + 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::DRACO_ERROR, "Failed to parse material file name"); + return true; + } + parser::SkipLine(&line_buffer); + + if (!material_file_name_.empty()) { + 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) { + const std::string full_path = GetFullPath(file_name, input_file_name_); + std::vector<char> buffer; + if (!ReadFileToBuffer(full_path, &buffer)) { + return false; + } + + // Backup the original decoder buffer. + DecoderBuffer old_buffer = buffer_; + + buffer_.Init(buffer.data(), buffer.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 == "newmtl") { + parser::SkipWhitespace(buffer()); + parser::ParseLine(buffer(), &str); + if (str.empty()) { + return false; + } + // Add new material to our map. + material_name_to_id_[str] = num_materials_++; + } + return true; +} + +} // namespace draco |