Changeset View
Changeset View
Standalone View
Standalone View
extern/gltf-draco/draco/src/draco/io/obj_encoder.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_encoder.h" | |||||
| #include <fstream> | |||||
| #include "draco/metadata/geometry_metadata.h" | |||||
| namespace draco { | |||||
| ObjEncoder::ObjEncoder() | |||||
| : pos_att_(nullptr), | |||||
| tex_coord_att_(nullptr), | |||||
| normal_att_(nullptr), | |||||
| material_att_(nullptr), | |||||
| sub_obj_att_(nullptr), | |||||
| out_buffer_(nullptr), | |||||
| in_point_cloud_(nullptr), | |||||
| in_mesh_(nullptr), | |||||
| current_sub_obj_id_(-1), | |||||
| current_material_id_(-1) {} | |||||
| bool ObjEncoder::EncodeToFile(const PointCloud &pc, | |||||
| const std::string &file_name) { | |||||
| std::ofstream file(file_name); | |||||
| if (!file) | |||||
| return false; // File could not be opened. | |||||
| file_name_ = file_name; | |||||
| // Encode the mesh into a buffer. | |||||
| EncoderBuffer buffer; | |||||
| if (!EncodeToBuffer(pc, &buffer)) | |||||
| return false; | |||||
| // Write the buffer into the file. | |||||
| file.write(buffer.data(), buffer.size()); | |||||
| return true; | |||||
| } | |||||
| bool ObjEncoder::EncodeToFile(const Mesh &mesh, const std::string &file_name) { | |||||
| in_mesh_ = &mesh; | |||||
| return EncodeToFile(static_cast<const PointCloud &>(mesh), file_name); | |||||
| } | |||||
| bool ObjEncoder::EncodeToBuffer(const PointCloud &pc, | |||||
| EncoderBuffer *out_buffer) { | |||||
| in_point_cloud_ = &pc; | |||||
| out_buffer_ = out_buffer; | |||||
| if (!EncodeInternal()) | |||||
| return ExitAndCleanup(false); | |||||
| return ExitAndCleanup(true); | |||||
| } | |||||
| bool ObjEncoder::EncodeToBuffer(const Mesh &mesh, EncoderBuffer *out_buffer) { | |||||
| in_mesh_ = &mesh; | |||||
| return EncodeToBuffer(static_cast<const PointCloud &>(mesh), out_buffer); | |||||
| } | |||||
| bool ObjEncoder::EncodeInternal() { | |||||
| pos_att_ = nullptr; | |||||
| tex_coord_att_ = nullptr; | |||||
| normal_att_ = nullptr; | |||||
| material_att_ = nullptr; | |||||
| sub_obj_att_ = nullptr; | |||||
| current_sub_obj_id_ = -1; | |||||
| current_material_id_ = -1; | |||||
| if (!GetSubObjects()) | |||||
| return false; | |||||
| if (!EncodeMaterialFileName()) | |||||
| return false; | |||||
| if (!EncodePositions()) | |||||
| return false; | |||||
| if (!EncodeTextureCoordinates()) | |||||
| return false; | |||||
| if (!EncodeNormals()) | |||||
| return false; | |||||
| if (in_mesh_ && !EncodeFaces()) | |||||
| return false; | |||||
| return true; | |||||
| } | |||||
| bool ObjEncoder::ExitAndCleanup(bool return_value) { | |||||
| in_mesh_ = nullptr; | |||||
| in_point_cloud_ = nullptr; | |||||
| out_buffer_ = nullptr; | |||||
| pos_att_ = nullptr; | |||||
| tex_coord_att_ = nullptr; | |||||
| normal_att_ = nullptr; | |||||
| material_att_ = nullptr; | |||||
| sub_obj_att_ = nullptr; | |||||
| current_sub_obj_id_ = -1; | |||||
| current_material_id_ = -1; | |||||
| file_name_.clear(); | |||||
| return return_value; | |||||
| } | |||||
| bool ObjEncoder::GetSubObjects() { | |||||
| const GeometryMetadata *pc_metadata = in_point_cloud_->GetMetadata(); | |||||
| if (!pc_metadata) | |||||
| return true; | |||||
| const AttributeMetadata *sub_obj_metadata = | |||||
| pc_metadata->GetAttributeMetadataByStringEntry("name", "sub_obj"); | |||||
| if (!sub_obj_metadata) | |||||
| return true; | |||||
| sub_obj_id_to_name_.clear(); | |||||
| for (const auto &entry : sub_obj_metadata->entries()) { | |||||
| // Sub-object id must be int. | |||||
| int value = 0; | |||||
| if (!entry.second.GetValue(&value)) | |||||
| continue; | |||||
| sub_obj_id_to_name_[value] = entry.first; | |||||
| } | |||||
| sub_obj_att_ = in_point_cloud_->GetAttributeByUniqueId( | |||||
| sub_obj_metadata->att_unique_id()); | |||||
| if (sub_obj_att_ == nullptr || sub_obj_att_->size() == 0) | |||||
| return false; | |||||
| return true; | |||||
| } | |||||
| bool ObjEncoder::EncodeMaterialFileName() { | |||||
| const GeometryMetadata *pc_metadata = in_point_cloud_->GetMetadata(); | |||||
| const AttributeMetadata *material_metadata = nullptr; | |||||
| if (pc_metadata) { | |||||
| material_metadata = | |||||
| pc_metadata->GetAttributeMetadataByStringEntry("name", "material"); | |||||
| } | |||||
| std::string material_file_name; | |||||
| std::string material_full_path; | |||||
| if (!material_metadata) | |||||
| return true; | |||||
| if (!material_metadata->GetEntryString("file_name", &material_file_name)) | |||||
| return false; | |||||
| buffer()->Encode("mtllib ", 7); | |||||
| buffer()->Encode(material_file_name.c_str(), material_file_name.size()); | |||||
| buffer()->Encode("\n", 1); | |||||
| material_id_to_name_.clear(); | |||||
| for (const auto &entry : material_metadata->entries()) { | |||||
| // Material id must be int. | |||||
| int value = 0; | |||||
| // Found entry that are not material id, e.g. file name as a string. | |||||
| if (!entry.second.GetValue(&value)) | |||||
| continue; | |||||
| material_id_to_name_[value] = entry.first; | |||||
| } | |||||
| material_att_ = in_point_cloud_->GetAttributeByUniqueId( | |||||
| material_metadata->att_unique_id()); | |||||
| if (material_att_ == nullptr || material_att_->size() == 0) | |||||
| return false; | |||||
| return true; | |||||
| } | |||||
| bool ObjEncoder::EncodePositions() { | |||||
| const PointAttribute *const att = | |||||
| in_point_cloud_->GetNamedAttribute(GeometryAttribute::POSITION); | |||||
| if (att == nullptr || att->size() == 0) | |||||
| return false; // Position attribute must be valid. | |||||
| std::array<float, 3> value; | |||||
| for (AttributeValueIndex i(0); i < static_cast<uint32_t>(att->size()); ++i) { | |||||
| if (!att->ConvertValue<float, 3>(i, &value[0])) | |||||
| return false; | |||||
| buffer()->Encode("v ", 2); | |||||
| EncodeFloatList(&value[0], 3); | |||||
| buffer()->Encode("\n", 1); | |||||
| } | |||||
| pos_att_ = att; | |||||
| return true; | |||||
| } | |||||
| bool ObjEncoder::EncodeTextureCoordinates() { | |||||
| const PointAttribute *const att = | |||||
| in_point_cloud_->GetNamedAttribute(GeometryAttribute::TEX_COORD); | |||||
| if (att == nullptr || att->size() == 0) | |||||
| return true; // It's OK if we don't have texture coordinates. | |||||
| std::array<float, 2> value; | |||||
| for (AttributeValueIndex i(0); i < static_cast<uint32_t>(att->size()); ++i) { | |||||
| if (!att->ConvertValue<float, 2>(i, &value[0])) | |||||
| return false; | |||||
| buffer()->Encode("vt ", 3); | |||||
| EncodeFloatList(&value[0], 2); | |||||
| buffer()->Encode("\n", 1); | |||||
| } | |||||
| tex_coord_att_ = att; | |||||
| return true; | |||||
| } | |||||
| bool ObjEncoder::EncodeNormals() { | |||||
| const PointAttribute *const att = | |||||
| in_point_cloud_->GetNamedAttribute(GeometryAttribute::NORMAL); | |||||
| if (att == nullptr || att->size() == 0) | |||||
| return true; // It's OK if we don't have normals. | |||||
| std::array<float, 3> value; | |||||
| for (AttributeValueIndex i(0); i < static_cast<uint32_t>(att->size()); ++i) { | |||||
| if (!att->ConvertValue<float, 3>(i, &value[0])) | |||||
| return false; | |||||
| buffer()->Encode("vn ", 3); | |||||
| EncodeFloatList(&value[0], 3); | |||||
| buffer()->Encode("\n", 1); | |||||
| } | |||||
| normal_att_ = att; | |||||
| return true; | |||||
| } | |||||
| bool ObjEncoder::EncodeFaces() { | |||||
| for (FaceIndex i(0); i < in_mesh_->num_faces(); ++i) { | |||||
| if (sub_obj_att_) | |||||
| if (!EncodeSubObject(i)) | |||||
| return false; | |||||
| if (material_att_) | |||||
| if (!EncodeMaterial(i)) | |||||
| return false; | |||||
| buffer()->Encode('f'); | |||||
| for (int j = 0; j < 3; ++j) { | |||||
| if (!EncodeFaceCorner(i, j)) | |||||
| return false; | |||||
| } | |||||
| buffer()->Encode("\n", 1); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| bool ObjEncoder::EncodeMaterial(FaceIndex face_id) { | |||||
| int material_id = 0; | |||||
| // Pick the first corner, all corners of a face should have same id. | |||||
| const PointIndex vert_index = in_mesh_->face(face_id)[0]; | |||||
| const AttributeValueIndex index_id(material_att_->mapped_index(vert_index)); | |||||
| if (!material_att_->ConvertValue<int>(index_id, &material_id)) { | |||||
| return false; | |||||
| } | |||||
| if (material_id != current_material_id_) { | |||||
| // Update material information. | |||||
| buffer()->Encode("usemtl ", 7); | |||||
| const auto mat_ptr = material_id_to_name_.find(material_id); | |||||
| // If the material id is not found. | |||||
| if (mat_ptr == material_id_to_name_.end()) | |||||
| return false; | |||||
| buffer()->Encode(mat_ptr->second.c_str(), mat_ptr->second.size()); | |||||
| buffer()->Encode("\n", 1); | |||||
| current_material_id_ = material_id; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| bool ObjEncoder::EncodeSubObject(FaceIndex face_id) { | |||||
| int sub_obj_id = 0; | |||||
| // Pick the first corner, all corners of a face should have same id. | |||||
| const PointIndex vert_index = in_mesh_->face(face_id)[0]; | |||||
| const AttributeValueIndex index_id(sub_obj_att_->mapped_index(vert_index)); | |||||
| if (!sub_obj_att_->ConvertValue<int>(index_id, &sub_obj_id)) { | |||||
| return false; | |||||
| } | |||||
| if (sub_obj_id != current_sub_obj_id_) { | |||||
| buffer()->Encode("o ", 2); | |||||
| const auto sub_obj_ptr = sub_obj_id_to_name_.find(sub_obj_id); | |||||
| if (sub_obj_ptr == sub_obj_id_to_name_.end()) | |||||
| return false; | |||||
| buffer()->Encode(sub_obj_ptr->second.c_str(), sub_obj_ptr->second.size()); | |||||
| buffer()->Encode("\n", 1); | |||||
| current_sub_obj_id_ = sub_obj_id; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| bool ObjEncoder::EncodeFaceCorner(FaceIndex face_id, int local_corner_id) { | |||||
| buffer()->Encode(' '); | |||||
| const PointIndex vert_index = in_mesh_->face(face_id)[local_corner_id]; | |||||
| // Note that in the OBJ format, all indices are encoded starting from index 1. | |||||
| // Encode position index. | |||||
| EncodeInt(pos_att_->mapped_index(vert_index).value() + 1); | |||||
| if (tex_coord_att_ || normal_att_) { | |||||
| // Encoding format is pos_index/tex_coord_index/normal_index. | |||||
| // If tex_coords are not present, we must encode pos_index//normal_index. | |||||
| buffer()->Encode('/'); | |||||
| if (tex_coord_att_) { | |||||
| EncodeInt(tex_coord_att_->mapped_index(vert_index).value() + 1); | |||||
| } | |||||
| if (normal_att_) { | |||||
| buffer()->Encode('/'); | |||||
| EncodeInt(normal_att_->mapped_index(vert_index).value() + 1); | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| void ObjEncoder::EncodeFloat(float val) { | |||||
| snprintf(num_buffer_, sizeof(num_buffer_), "%f", val); | |||||
| buffer()->Encode(num_buffer_, strlen(num_buffer_)); | |||||
| } | |||||
| void ObjEncoder::EncodeFloatList(float *vals, int num_vals) { | |||||
| for (int i = 0; i < num_vals; ++i) { | |||||
| if (i > 0) { | |||||
| buffer()->Encode(' '); | |||||
| } | |||||
| EncodeFloat(vals[i]); | |||||
| } | |||||
| } | |||||
| void ObjEncoder::EncodeInt(int32_t val) { | |||||
| snprintf(num_buffer_, sizeof(num_buffer_), "%d", val); | |||||
| buffer()->Encode(num_buffer_, strlen(num_buffer_)); | |||||
| } | |||||
| } // namespace draco | |||||