blob: c092805d27813fb91573cd77c00ef08962c1da54 [file] [log] [blame]
// Copyright 2016 The Android Open Source Project
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "android/skin/qt/wavefront-obj-parser.h"
#include <QString>
#include <tuple>
#include <unordered_map>
namespace std {
template <>
struct hash<std::tuple<int, int, int>> {
size_t operator()(const tuple<int, int, int>& t) const {
return hash_combine(std::hash<int>()(get<0>(t)),
hash_combine(std::hash<int>()(get<1>(t)),
std::hash<int>()(get<2>(t))));
}
private:
static int hash_combine(int seed, int v) {
return std::hash<int>()(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
};
}
bool parseWavefrontOBJ(QTextStream& stream,
std::vector<float>& vtx_buf,
std::vector<GLuint>& idx_buf) {
std::vector<float> pos, tex, norm;
std::unordered_map<std::tuple<int, int, int>, size_t> idx_table;
QString str;
int vertices = 0;
vtx_buf.clear();
idx_buf.clear();
for (stream >> str; !stream.atEnd(); stream >> str) {
if (str.length() <= 0) {
continue;
}
if (str[0] == '#') {
// Comment, read till the end of line.
stream.readLine();
} else if (str == "v" || str == "vn") {
// Vertex position or normal.
// Both are specified with 3 floating point numbers
// separated by whitespace.
float xyz[3];
stream >> xyz[0] >> xyz[1] >> xyz[2];
if (stream.status() != QTextStream::Ok) {
qWarning("OBJ parser: invalid position or normal");
return false;
}
auto& container = (str == "v" ? pos : norm);
container.insert(container.end(), xyz, xyz + 3);
} else if (str == "vt") {
// UV coords.
// Specified with 2 floating point numbers separated by whitespace.
float uv[2];
stream >> uv[0] >> uv[1];
if (stream.status() != QTextStream::Ok) {
qWarning("OBJ parser: invalid UV");
return false;
}
tex.insert(tex.end(), uv, uv + 2);
} else if (str == "f") {
// Face.
// A face is specified with 3 vertices.
// A vertex is specified like "vp/vt/vn" (no spaces)
// where vp, vt and vn are indices into the position,
// UV and normal arrays.
QString v;
size_t vp, vt, vn;
for (size_t i = 0; i < 3; i++) {
stream >> v;
QStringList components = v.split('/', QString::SkipEmptyParts);
if (components.size() != 3) {
qWarning("OBJ parser: invalid face specification");
return false;
}
bool pos_result, tex_result, nrm_result;
// Note that indices in OBJ are 1-based.
vp = components[0].toInt(&pos_result) - 1;
vt = components[1].toInt(&tex_result) - 1;
vn = components[2].toInt(&nrm_result) - 1;
if (!(pos_result && tex_result && nrm_result) ||
vp * 3 >= pos.size() ||
vt * 2 >= tex.size() ||
vn * 3 >= norm.size()) {
qWarning("OBJ parser: invalid face specification");
return false;
}
auto vertex_idx = std::make_tuple(vp, vt, vn);
auto vertex_it = idx_table.find(vertex_idx);
if (vertex_it == idx_table.end()) {
// First time encountering this vertex, write its attributes
// into vertex buffer.
size_t element_array_idx = vertices++;
idx_buf.push_back(element_array_idx);
vtx_buf.insert(vtx_buf.end(), &pos[vp * 3], &pos[vp * 3 + 3]);
vtx_buf.insert(vtx_buf.end(), &norm[vn * 3], &norm[vn * 3 + 3]);
vtx_buf.insert(vtx_buf.end(), &tex[vt * 2], &tex[vt * 2 + 2]);
idx_table[vertex_idx] = element_array_idx;
} else {
// We've already encountered this vertex, write its index into the
// index buffer.
idx_buf.push_back(vertex_it->second);
}
}
} else {
// Something's wrong, bail out.
qWarning("OBJ parser: invalid input [%s]", str.toStdString().c_str());
return false;
}
}
return true;
}