celestia/src/tools/cmod/common/convertobj.cpp

399 lines
13 KiB
C++

// convertobj.cpp
//
// Copyright (C) 2004-2010, Chris Laurel <claurel@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// Functions for converting a Wavefront .obj file into a
// Celestia model (cmod)
#include <cstdio>
#include <sstream>
#include <celmodel/material.h>
#include <celmodel/mesh.h>
#include <celmodel/model.h>
#include "convertobj.h"
namespace
{
std::string::size_type getToken(const std::string& s, std::string::size_type start, std::string& token)
{
std::string::size_type pos = start;
token.clear();
while (pos < s.size() && isspace(s[pos]))
{
pos++;
}
while (pos < s.size() && !isspace(s[pos]))
{
token += s[pos];
pos++;
}
return pos;
}
// Convert a 1-based array index to a zero based index. Negative
// indices are relative to the top of the end of the array. Return
// -1 if the index is invalid.
int convertIndex(int index, unsigned int maxValue)
{
if (index > 0)
{
if (index <= static_cast<int>(maxValue))
return index - 1;
return -1;
}
if (index < 0)
{
if (-index <= static_cast<int>(maxValue))
return static_cast<int>(maxValue) + index;
return -1;
}
return -1;
}
} // end unnamed namespace
WavefrontLoader::WavefrontLoader(std::istream& in) :
m_in(in),
m_lineNumber(0),
m_model(nullptr)
{
}
cmod::Model*
WavefrontLoader::load()
{
std::string line;
std::string keyword;
unsigned int vertexCount = 0;
ObjVertex::VertexType lastVertexType = ObjVertex::Point;
int currentMaterialIndex = -1;
m_model = new cmod::Model();
while (getline(m_in, line))
{
m_lineNumber++;
// strip comments
std::string::size_type commentPos = line.find('#');
if (commentPos != std::string::npos)
{
line = line.substr(0, commentPos);
}
std::string::size_type pos = getToken(line, 0, keyword);
if (!keyword.empty())
{
if (keyword == "v")
{
Eigen::Vector3f v(Eigen::Vector3f::Zero());
if (std::sscanf(line.c_str() + pos, "%f %f %f", &v.x(), &v.y(), &v.z()) != 3)
{
reportError("Bad vertex");
return nullptr;
}
m_vertices.push_back(v);
}
else if (keyword == "vn")
{
Eigen::Vector3f v(Eigen::Vector3f::Zero());
if (std::sscanf(line.c_str() + pos, "%f %f %f", &v.x(), &v.y(), &v.z()) != 3)
{
reportError("Bad normal");
return nullptr;
}
m_normals.push_back(v);
}
else if (keyword == "vt")
{
Eigen::Vector2f v(Eigen::Vector2f::Zero());
if (std::sscanf(line.c_str() + pos, "%f %f", &v.x(), &v.y()) != 2)
{
reportError("Bad texture coordinate");
return nullptr;
}
m_texCoords.push_back(v);
}
else if (keyword == "usemtl")
{
cmod::Material* material = new cmod::Material();
material->diffuse = cmod::Color(1.0f, 1.0f, 1.0f);
currentMaterialIndex = m_model->addMaterial(material) - 1;
if (!m_materialGroups.empty())
{
if (m_materialGroups.back().firstIndex == (int) m_indexData.size())
{
m_materialGroups.back().materialIndex = currentMaterialIndex;
}
else
{
MaterialGroup group;
group.materialIndex = currentMaterialIndex;
group.firstIndex = m_indexData.size();
m_materialGroups.push_back(group);
}
}
}
else if (keyword == "f")
{
std::vector<ObjVertex> faceVertices;
while (pos < line.size())
{
std::string vertexString;
pos = getToken(line, pos, vertexString);
if (!vertexString.empty())
{
ObjVertex vertex;
if (std::sscanf(vertexString.c_str(), "%d/%d/%d", &vertex.vertexIndex, &vertex.texCoordIndex, &vertex.normalIndex) == 3)
{
// Vertex, texture coordinate, and normal
}
else if (std::sscanf(vertexString.c_str(), "%d//%d", &vertex.vertexIndex, &vertex.normalIndex) == 2)
{
// Vertex + normal
}
else if (std::sscanf(vertexString.c_str(), "%d/%d", &vertex.vertexIndex, &vertex.texCoordIndex) == 2)
{
// Vertex + texture coordinate
}
else if (std::sscanf(vertexString.c_str(), "%d", &vertex.vertexIndex) == 1)
{
// Vertex only
}
else
{
reportError("Bad vertex in face");
return nullptr;
}
faceVertices.push_back(vertex);
unsigned int vertexCount = faceVertices.size();
if (vertexCount > 1)
{
if (faceVertices[vertexCount - 1].type() != faceVertices[vertexCount - 2].type())
{
reportError("Vertices in face have mismatched type");
return nullptr;
}
}
}
}
if (faceVertices.size() < 3)
{
reportError("Face has less than three vertices");
return nullptr;
}
ObjVertex::VertexType faceVertexType = faceVertices.front().type();
bool isFirstVertex = (vertexCount == 0);
bool vertexTypeChanged = (faceVertexType != lastVertexType);
if (isFirstVertex)
{
MaterialGroup group;
group.materialIndex = currentMaterialIndex;
group.firstIndex = 0;
m_materialGroups.push_back(group);
}
else if (vertexTypeChanged)
{
createMesh(lastVertexType, vertexCount);
vertexCount = 0;
MaterialGroup group;
group.materialIndex = currentMaterialIndex;
group.firstIndex = 0;
m_materialGroups.push_back(group);
}
lastVertexType = faceVertexType;
for (unsigned int i = 0; i < faceVertices.size(); ++i)
{
ObjVertex vertex = faceVertices[i];
int index = convertIndex(vertex.vertexIndex, m_vertices.size());
if (index >= 0)
{
addVertexData(m_vertices[index]);
}
else
{
reportError("Face has bad vertex index");
return nullptr;
}
if (vertex.hasNormal())
{
int index = convertIndex(vertex.normalIndex, m_normals.size());
if (index >= 0)
{
addVertexData(m_normals[index]);
}
else
{
reportError("Face has bad normal index");
}
}
if (vertex.hasTexCoord())
{
int index = convertIndex(vertex.texCoordIndex, m_texCoords.size());
if (index >= 0)
{
addVertexData(m_texCoords[index]);
}
else
{
reportError("Face has bad texture coordinate index");
}
}
}
// Triangulate the face. This simple scheme will not work for most non-convex
// polygons, so we're assuming some simple geometry.
for (unsigned int i = 0; i < faceVertices.size() - 2; ++i)
{
m_indexData.push_back(vertexCount);
m_indexData.push_back(vertexCount + i + 1);
m_indexData.push_back(vertexCount + i + 2);
}
vertexCount += faceVertices.size();
}
else
{
// Ignore unrecognized keywords
//cout << keyword << endl;
}
}
}
// Add the final mesh
if (vertexCount > 0)
{
createMesh(lastVertexType, vertexCount);
}
return m_model;
}
void
WavefrontLoader::reportError(const std::string& message)
{
//cerr << message << endl;
std::ostringstream os;
os << "Line " << m_lineNumber << ": " << message;
m_errorMessage = os.str();
delete m_model;
m_model = nullptr;
}
void
WavefrontLoader::addVertexData(const Eigen::Vector2f& v)
{
m_vertexData.push_back(v.x());
m_vertexData.push_back(v.y());
}
void
WavefrontLoader::addVertexData(const Eigen::Vector3f& v)
{
m_vertexData.push_back(v.x());
m_vertexData.push_back(v.y());
m_vertexData.push_back(v.z());
}
void
WavefrontLoader::createMesh(ObjVertex::VertexType vertexType, unsigned int vertexCount)
{
cmod::VertexAttribute attributes[8];
unsigned int nAttributes = 0;
unsigned int offset = 0;
// Position attribute is always present
attributes[nAttributes] = cmod::VertexAttribute(cmod::VertexAttributeSemantic::Position,
cmod::VertexAttributeFormat::Float3,
0);
nAttributes++;
offset += 12;
if (vertexType == ObjVertex::PointNormal || vertexType == ObjVertex::PointTexNormal)
{
attributes[nAttributes] = cmod::VertexAttribute(cmod::VertexAttributeSemantic::Normal,
cmod::VertexAttributeFormat::Float3,
offset);
nAttributes++;
offset += 12;
}
if (vertexType == ObjVertex::PointTex || vertexType == ObjVertex::PointTexNormal)
{
attributes[nAttributes] = cmod::VertexAttribute(cmod::VertexAttributeSemantic::Texture0,
cmod::VertexAttributeFormat::Float2,
offset);
nAttributes++;
offset += 8;
}
auto* vertexDataCopy = new float[m_vertexData.size()];
copy(m_vertexData.begin(), m_vertexData.end(), vertexDataCopy);
// Create the Celestia mesh
cmod::Mesh* mesh = new cmod::Mesh();
mesh->setVertexDescription(cmod::VertexDescription(offset, nAttributes, attributes));
mesh->setVertices(vertexCount, vertexDataCopy);
// Add primitive groups
for (unsigned int i = 0; i < m_materialGroups.size(); ++i)
{
unsigned int indexCount;
unsigned int firstIndex = m_materialGroups[i].firstIndex;
if (i < m_materialGroups.size() - 1)
{
indexCount = m_materialGroups[i + 1].firstIndex - firstIndex;
}
else
{
indexCount = m_indexData.size() - firstIndex;
}
if (indexCount > 0)
{
cmod::index32* indexDataCopy = new cmod::index32[indexCount];
copy(m_indexData.begin() + firstIndex, m_indexData.begin() + firstIndex + indexCount, indexDataCopy);
mesh->addGroup(cmod::PrimitiveGroupType::TriList, m_materialGroups[i].materialIndex, indexCount, indexDataCopy);
}
}
m_vertexData.clear();
m_indexData.clear();
m_materialGroups.clear();
m_model->addMesh(mesh);
}