// convertobj.cpp // // Copyright (C) 2004-2010, Chris Laurel // // 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 #include #include #include #include #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(maxValue)) return index - 1; return -1; } if (index < 0) { if (-index <= static_cast(maxValue)) return static_cast(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 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); }