/* * Copyright 2020 The Android Open Source Project * * 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 "ObjReader.h" #include #include #include #include #include "MtlReader.h" #include "core_lib.h" #include namespace android { namespace hardware { namespace automotive { namespace sv { namespace V1_0 { namespace implementation { using android_auto::surround_view::CarMaterial; using android_auto::surround_view::CarVertex; namespace { constexpr int kNumberOfVerticesPerFace = 3; constexpr int kNumberOfAxes = 3; constexpr int kCharBufferSize = 128; const std::array kMat4Identity = { /*row 0*/ 1, 0, 0, 0, /*row 1*/ 0, 1, 0, 0, /*row 2*/ 0, 0, 1, 0, /*row 3*/ 0, 0, 0, 1}; // Copies face vertices parsed from obj to car vertices. void CopyFaceToCarVertex(const std::vector>& currentVertices, const std::vector>& currentTextures, const std::vector>& currentNormals, int vertexId, int textureId, int normalId, CarVertex* carVertex) { std::memcpy(carVertex->pos.data(), currentVertices[vertexId - 1].data(), currentVertices[vertexId - 1].size() * sizeof(float)); if (textureId != -1) { std::memcpy(carVertex->tex_coord.data(), currentTextures[textureId - 1].data(), 2 * sizeof(float)); // Set texture coodinates as invalid. carVertex->tex_coord = {-1.0, -1.0}; } std::memcpy(carVertex->normal.data(), currentNormals[normalId - 1].data(), currentNormals[normalId - 1].size() * sizeof(float)); } } // namespace bool ReadObjFromFile(const std::string& objFilename, std::map* carPartsMap) { return ReadObjFromFile(objFilename, ReadObjOptions(), carPartsMap); } bool ReadObjFromFile(const std::string& objFilename, const ReadObjOptions& option, std::map* carPartsMap) { FILE* file = fopen(objFilename.c_str(), "r"); if (!file) { LOG(ERROR) << "Failed to open obj file: " << objFilename; return false; } for (int i = 0; i < kNumberOfAxes; ++i) { if (option.coordinateMapping[i] >= kNumberOfAxes || option.coordinateMapping[i] < 0) { fclose(file); LOG(ERROR) << "coordinateMapping index must be less than 3 and greater or equal " "to 0."; return false; } } std::vector> currentVertices; std::vector> currentNormals; std::vector> currentTextures; std::map mtlConfigParamsMap; std::string currentGroupName; MtlConfigParams currentMtlConfig; while (true) { char lineHeader[kCharBufferSize]; // read the first word of the line int res = fscanf(file, "%s", lineHeader); if (res == EOF) { break; // EOF = End Of File. Quit the loop. } if (strcmp(lineHeader, "#") == 0) { fgets(lineHeader, sizeof(lineHeader), file); continue; } // TODO(b/156558814): add object type support. // TODO(b/156559272): add document for supported format. // Only single group per line is supported. if (strcmp(lineHeader, "g") == 0) { res = fscanf(file, "%s", lineHeader); currentGroupName = lineHeader; currentMtlConfig = MtlConfigParams(); if (carPartsMap->find(currentGroupName) != carPartsMap->end()) { LOG(WARNING) << "Duplicate group name: " << currentGroupName << ". using car part name as: " << currentGroupName << "_dup"; currentGroupName.append("_dup"); } carPartsMap->emplace( std::make_pair(currentGroupName, CarPart((std::vector()), CarMaterial(), kMat4Identity, std::string(), std::vector()))); continue; } // no "g" case, assign it as default. if (currentGroupName.empty()) { currentGroupName = "default"; currentMtlConfig = MtlConfigParams(); carPartsMap->emplace( std::make_pair(currentGroupName, CarPart((std::vector()), CarMaterial(), kMat4Identity, std::string(), std::vector()))); } if (strcmp(lineHeader, "usemtl") == 0) { res = fscanf(file, "%s", lineHeader); // If material name not found. if (mtlConfigParamsMap.find(lineHeader) == mtlConfigParamsMap.end()) { carPartsMap->at(currentGroupName).material = CarMaterial(); LOG(ERROR) << "Material not found: $0" << lineHeader; return false; } currentMtlConfig = mtlConfigParamsMap[lineHeader]; carPartsMap->at(currentGroupName).material.ka = {currentMtlConfig.ka[0], currentMtlConfig.ka[1], currentMtlConfig.ka[2]}; carPartsMap->at(currentGroupName).material.kd = {currentMtlConfig.kd[0], currentMtlConfig.kd[1], currentMtlConfig.kd[2]}; carPartsMap->at(currentGroupName).material.ks = {currentMtlConfig.ks[0], currentMtlConfig.ks[1], currentMtlConfig.ks[2]}; carPartsMap->at(currentGroupName).material.d = currentMtlConfig.d; carPartsMap->at(currentGroupName).material.textures.clear(); continue; } if (strcmp(lineHeader, "mtllib") == 0) { res = fscanf(file, "%s", lineHeader); mtlConfigParamsMap.clear(); std::string mtlFilename; if (option.mtlFilename.empty()) { mtlFilename = objFilename.substr(0, objFilename.find_last_of("/")); mtlFilename.append("/"); mtlFilename.append(lineHeader); } else { mtlFilename = option.mtlFilename; } if (!ReadMtlFromFile(mtlFilename, &mtlConfigParamsMap)) { LOG(ERROR) << "Parse MTL file " << mtlFilename << " failed."; return false; } continue; } if (strcmp(lineHeader, "v") == 0) { std::array pos; fscanf(file, "%f %f %f\n", &pos[option.coordinateMapping[0]], &pos[option.coordinateMapping[1]], &pos[option.coordinateMapping[2]]); for (int i = 0; i < kNumberOfAxes; ++i) { pos[i] *= option.scales[i]; pos[i] += option.offsets[i]; } currentVertices.push_back(pos); } else if (strcmp(lineHeader, "vt") == 0) { std::array texture; fscanf(file, "%f %f %f\n", &texture[0], &texture[1], &texture[2]); currentTextures.push_back(texture); } else if (strcmp(lineHeader, "vn") == 0) { std::array normal; fscanf(file, "%f %f %f\n", &normal[option.coordinateMapping[0]], &normal[option.coordinateMapping[1]], &normal[option.coordinateMapping[2]]); currentNormals.push_back(normal); } else if (strcmp(lineHeader, "f") == 0) { int vertexId[kNumberOfVerticesPerFace]; int textureId[kNumberOfVerticesPerFace] = {-1, -1, -1}; int normalId[kNumberOfVerticesPerFace]; // Face vertices supported formats: // With texture: pos/texture/normal // Without texture: pos//normal // Scan first vertex position. int matches = fscanf(file, "%d/", &vertexId[0]); if (matches != 1) { LOG(WARNING) << "Face index error. Skipped."; fgets(lineHeader, sizeof(lineHeader), file); continue; } // Try scanning first two face 2 vertices with texture format present. bool isTexturePresent = true; matches = fscanf(file, "%d/%d %d/%d/%d", &textureId[0], &normalId[0], &vertexId[1], &textureId[1], &normalId[1]); // If 5 matches not found, try scanning first 2 face vertices without // texture format. if (matches != 5) { matches = fscanf(file, "/%d %d//%d", &normalId[0], &vertexId[1], &normalId[1]); // If 3 matches not found return with error. if (matches != 3) { LOG(WARNING) << "Face format not supported. Skipped."; fgets(lineHeader, sizeof(lineHeader), file); continue; } isTexturePresent = false; } // Copy first two face vertices to car vertices. std::array carVertices; CopyFaceToCarVertex(currentVertices, currentTextures, currentNormals, vertexId[0], textureId[0], normalId[0], &carVertices[0]); CopyFaceToCarVertex(currentVertices, currentTextures, currentNormals, vertexId[1], textureId[1], normalId[1], &carVertices[1]); // Add a triangle that the first two vertices make with every subsequent // face vertex 3 and onwards. Note this assumes the face is a convex // polygon. do { if (isTexturePresent) { matches = fscanf(file, " %d/%d/%d", &vertexId[2], &textureId[2], &normalId[2]); // Warn if un-expected number of matches. if (matches != 3 && matches != 0) { LOG(WARNING) << "Face matches, expected 3, read: " << matches; break; } } else { // Warn if un-expected number of matches. matches = fscanf(file, " %d//%d", &vertexId[2], &normalId[2]); if (matches != 2 && matches != 0) { LOG(WARNING) << "Face matches, expected 2, read: " << matches; break; } } if (matches == 0) { break; } CopyFaceToCarVertex(currentVertices, currentTextures, currentNormals, vertexId[2], textureId[2], normalId[2], &carVertices[2]); carPartsMap->at(currentGroupName).vertices.push_back(carVertices[0]); carPartsMap->at(currentGroupName).vertices.push_back(carVertices[1]); carPartsMap->at(currentGroupName).vertices.push_back(carVertices[2]); carVertices[1] = carVertices[2]; } while (true); } else { // LOG(WARNING) << "Unknown tag " << lineHeader << ". Skipped"; fgets(lineHeader, sizeof(lineHeader), file); continue; } } fclose(file); return true; } } // namespace implementation } // namespace V1_0 } // namespace sv } // namespace automotive } // namespace hardware } // namespace android