306 lines
12 KiB
C++
306 lines
12 KiB
C++
/*
|
|
* 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 <array>
|
|
#include <cstdio>
|
|
#include <filesystem>
|
|
#include <vector>
|
|
|
|
#include "MtlReader.h"
|
|
#include "core_lib.h"
|
|
|
|
#include <android-base/logging.h>
|
|
|
|
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<float, 16> 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<std::array<float, kNumberOfAxes>>& currentVertices,
|
|
const std::vector<std::array<float, kNumberOfAxes>>& currentTextures,
|
|
const std::vector<std::array<float, kNumberOfAxes>>& 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<std::string, CarPart>* carPartsMap) {
|
|
return ReadObjFromFile(objFilename, ReadObjOptions(), carPartsMap);
|
|
}
|
|
|
|
bool ReadObjFromFile(const std::string& objFilename, const ReadObjOptions& option,
|
|
std::map<std::string, CarPart>* 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<std::array<float, kNumberOfAxes>> currentVertices;
|
|
std::vector<std::array<float, kNumberOfAxes>> currentNormals;
|
|
std::vector<std::array<float, kNumberOfAxes>> currentTextures;
|
|
std::map<std::string, MtlConfigParams> 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<CarVertex>()), CarMaterial(), kMat4Identity,
|
|
std::string(), std::vector<std::string>())));
|
|
continue;
|
|
}
|
|
|
|
// no "g" case, assign it as default.
|
|
if (currentGroupName.empty()) {
|
|
currentGroupName = "default";
|
|
currentMtlConfig = MtlConfigParams();
|
|
carPartsMap->emplace(
|
|
std::make_pair(currentGroupName,
|
|
CarPart((std::vector<CarVertex>()), CarMaterial(), kMat4Identity,
|
|
std::string(), std::vector<std::string>())));
|
|
}
|
|
|
|
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<float, kNumberOfAxes> 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<float, kNumberOfAxes> 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<float, kNumberOfAxes> 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<CarVertex, kNumberOfVerticesPerFace> 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
|