diff --git a/src/tools/cmod/3dstocmod.cpp b/src/tools/cmod/3dstocmod.cpp index 3111513c2..b5cb7312a 100644 --- a/src/tools/cmod/3dstocmod.cpp +++ b/src/tools/cmod/3dstocmod.cpp @@ -10,6 +10,7 @@ // Convert a 3DS file to a Celestia mesh (.cmod) file #include "convert3ds.h" +#include "cmodops.h" #include #include #include @@ -53,6 +54,29 @@ int main(int argc, char* argv[]) return 1; } + // Generate normals for the model + double smoothAngle = 45.0; // degrees + double weldTolerance = 1.0e-6; + bool weldVertices = true; + + Model* newModel = GenerateModelNormals(*model, float(smoothAngle * 3.14159265 / 180.0), weldVertices, weldTolerance); + delete model; + + if (!newModel) + { + cerr << "Ran out of memory while generating surface normals.\n"; + return 1; + } + + // Automatically uniquify vertices + for (unsigned int i = 0; newModel->getMesh(i) != NULL; i++) + { + Mesh* mesh = newModel->getMesh(i); + UniquifyVertices(*mesh); + } + + model = newModel; + #if 0 // Print information about primitive groups for (uint32 i = 0; model->getMesh(i); i++) diff --git a/src/tools/cmod/cmodops.cpp b/src/tools/cmod/cmodops.cpp index 8e4f67f2a..cdeb2d255 100644 --- a/src/tools/cmod/cmodops.cpp +++ b/src/tools/cmod/cmodops.cpp @@ -1236,6 +1236,72 @@ MergeModelMeshes(const Model& model) } +static Material* +cloneMaterial(const Material* other) +{ + Material* material = new Material(); + material->diffuse = other->diffuse; + material->specular = other->specular; + material->emissive = other->emissive; + material->specularPower = other->specularPower; + material->opacity = other->opacity; + material->blend = other->blend; + for (int i = 0; i < Material::TextureSemanticMax; ++i) + { + if (other->maps[i]) + { + material->maps[i] = new Material::DefaultTextureResource(other->maps[i]->source()); + } + } + + return material; +} + + +/*! Generate normals for an entire model. Return the new model, or null if + * normal generation failed due to an out of memory error. + */ +Model* +GenerateModelNormals(const Model& model, float smoothAngle, bool weldVertices, float weldTolerance) +{ + Model* newModel = new Model(); + + // Copy materials + for (unsigned int i = 0; model.getMaterial(i) != NULL; i++) + { + newModel->addMaterial(cloneMaterial(model.getMaterial(i))); + } + + bool ok = true; + for (unsigned int i = 0; model.getMesh(i) != NULL; i++) + { + Mesh* mesh = model.getMesh(i); + Mesh* newMesh = NULL; + + newMesh = GenerateNormals(*mesh, smoothAngle, weldVertices, weldTolerance); + if (newMesh == NULL) + { + ok = false; + } + else + { + newModel->addMesh(newMesh); + } + } + + if (!ok) + { + // If all of the meshes weren't processed due to an out of memory error, + // delete the new model and return NULL rather than a partially processed + // model. + delete newModel; + newModel = NULL; + } + + return newModel; +} + + #ifdef TRISTRIP bool convertToStrips(Mesh& mesh) diff --git a/src/tools/cmod/cmodops.h b/src/tools/cmod/cmodops.h index c3719ce7b..1fe6b42ae 100644 --- a/src/tools/cmod/cmodops.h +++ b/src/tools/cmod/cmodops.h @@ -39,10 +39,15 @@ struct Face }; +// Mesh operations extern cmod::Mesh* GenerateNormals(const cmod::Mesh& mesh, float smoothAngle, bool weld, float weldTolerance = 0.0f); extern cmod::Mesh* GenerateTangents(const cmod::Mesh& mesh, bool weld); extern bool UniquifyVertices(cmod::Mesh& mesh); + +// Model operations extern cmod::Model* MergeModelMeshes(const cmod::Model& model); +extern cmod::Model* GenerateModelNormals(const cmod::Model& model, float smoothAngle, bool weldVertices, float weldTolerance); + template void JoinVertices(std::vector& faces, @@ -86,10 +91,10 @@ JoinVertices(std::vector& faces, uint32 uniqueCount = 0; for (uint32 i = 0; i < nVertices; i++) { - if (i == 0 || !equivalencePredicate(vertices[i - 1], vertices[i])) - { + if (i == 0 || !equivalencePredicate(vertices[i - 1], vertices[i])) + { lastUnique = i; - uniqueCount++; + uniqueCount++; } mergeMap[vertices[i].index] = vertices[lastUnique].index; @@ -99,7 +104,9 @@ JoinVertices(std::vector& faces, for (f = 0; f < faces.size(); f++) { for (uint32 k= 0; k < 3; k++) + { faces[f].vi[k] = mergeMap[faces[f].i[k]]; + } } } diff --git a/src/tools/cmod/convert3ds.cpp b/src/tools/cmod/convert3ds.cpp index bfe880aaa..b52797557 100644 --- a/src/tools/cmod/convert3ds.cpp +++ b/src/tools/cmod/convert3ds.cpp @@ -55,178 +55,33 @@ Convert3DSMesh(Model& model, const M3DScene& scene, const string& meshName) { - int nFaces = mesh3ds.getFaceCount(); int nVertices = mesh3ds.getVertexCount(); int nTexCoords = mesh3ds.getTexCoordCount(); - bool smooth = (mesh3ds.getSmoothingGroupCount() == nFaces); - bool hasTexCoords = (nTexCoords == nVertices); - int vertexSize = hasTexCoords ? 8 : 6; - int i; - - Vector3f* faceNormals = new Vector3f[nFaces]; - Vector3f* vertexNormals = new Vector3f[nFaces * 3]; - int* faceCounts = new int[nVertices]; - int** vertexFaces = new int*[nVertices]; - - int nOutputVertices = nFaces * 3; - float* vertices = new float[nOutputVertices * vertexSize]; - - - for (i = 0; i < nVertices; i++) + bool hasTexCoords = (nTexCoords >= nVertices); + int vertexSize = 3; + if (hasTexCoords) { - faceCounts[i] = 0; - vertexFaces[i] = NULL; + vertexSize += 2; } - // generate face normals - for (i = 0; i < nFaces; i++) + float* vertices = new float[mesh3ds.getVertexCount() * vertexSize]; + + // Build the vertex list + for (int i = 0; i < mesh3ds.getVertexCount(); ++i) { - uint16 v0, v1, v2; - mesh3ds.getFace(i, v0, v1, v2); + int k = i * vertexSize; + Vector3f pos = mesh3ds.getVertex(i); + vertices[k + 0] = pos.x(); + vertices[k + 1] = pos.y(); + vertices[k + 2] = pos.z(); - faceCounts[v0]++; - faceCounts[v1]++; - faceCounts[v2]++; - - Vector3f p0 = mesh3ds.getVertex(v0); - Vector3f p1 = mesh3ds.getVertex(v1); - Vector3f p2 = mesh3ds.getVertex(v2); - faceNormals[i] = (p1 - p0).cross(p2 - p1); - faceNormals[i].normalize(); - } - - if (!smooth && 0) - { - for (i = 0; i < nFaces; i++) + if (hasTexCoords) { - vertexNormals[i * 3] = faceNormals[i]; - vertexNormals[i * 3 + 1] = faceNormals[i]; - vertexNormals[i * 3 + 2] = faceNormals[i]; + Vector2f texCoord = mesh3ds.getTexCoord(i); + vertices[k + 3] = texCoord.x(); + vertices[k + 4] = texCoord.y(); } } - else - { - // allocate space for vertex face indices - for (i = 0; i < nVertices; i++) - { - vertexFaces[i] = new int[faceCounts[i] + 1]; - vertexFaces[i][0] = faceCounts[i]; - } - - for (i = 0; i < nFaces; i++) - { - uint16 v0, v1, v2; - mesh3ds.getFace(i, v0, v1, v2); - vertexFaces[v0][faceCounts[v0]--] = i; - vertexFaces[v1][faceCounts[v1]--] = i; - vertexFaces[v2][faceCounts[v2]--] = i; - } - - // average face normals to compute the vertex normals - for (i = 0; i < nFaces; i++) - { - uint16 v0, v1, v2; - mesh3ds.getFace(i, v0, v1, v2); - // uint32 smoothingGroups = mesh3ds.getSmoothingGroups(i); - - int j; - Vector3f v = Vector3f(0, 0, 0); - for (j = 1; j <= vertexFaces[v0][0]; j++) - { - int k = vertexFaces[v0][j]; - // if (k == i || (smoothingGroups & mesh3ds.getSmoothingGroups(k)) != 0) - if (faceNormals[i].dot(faceNormals[k]) > 0.5f) - { - v += faceNormals[k]; - } - } - if (v.squaredNorm() == 0.0f) - { - v = Vector3f::UnitX(); - } - v.normalize(); - vertexNormals[i * 3] = v; - - v = Vector3f(0, 0, 0); - for (j = 1; j <= vertexFaces[v1][0]; j++) - { - int k = vertexFaces[v1][j]; - // if (k == i || (smoothingGroups & mesh3ds.getSmoothingGroups(k)) != 0) - if (faceNormals[i].dot(faceNormals[k]) > 0.5f) - { - v += faceNormals[k]; - } - } - if (v.squaredNorm() == 0.0f) - { - v = Vector3f::UnitX(); - } - - v.normalize(); - vertexNormals[i * 3 + 1] = v; - - v = Vector3f(0, 0, 0); - for (j = 1; j <= vertexFaces[v2][0]; j++) - { - int k = vertexFaces[v2][j]; - // if (k == i || (smoothingGroups & mesh3ds.getSmoothingGroups(k)) != 0) - if (faceNormals[i].dot(faceNormals[k]) > 0.5f) - { - v += faceNormals[k]; - } - } - if (v.squaredNorm() == 0.0f) - { - v = Vector3f::UnitX(); - } - v.normalize(); - vertexNormals[i * 3 + 2] = v; - } - } - - // build the triangle list - for (i = 0; i < nFaces; i++) - { - uint16 triVert[3]; - mesh3ds.getFace(i, triVert[0], triVert[1], triVert[2]); - - for (int j = 0; j < 3; j++) - { - Vector3f pos = mesh3ds.getVertex(triVert[j]); - Vector3f norm = vertexNormals[i * 3 + j]; - int k = (i * 3 + j) * vertexSize; - - vertices[k + 0] = pos.x(); - vertices[k + 1] = pos.y(); - vertices[k + 2] = pos.z(); - vertices[k + 3] = norm.x(); - vertices[k + 4] = norm.y(); - vertices[k + 5] = norm.z(); - if (hasTexCoords) - { - vertices[k + 6] = mesh3ds.getTexCoord(triVert[j]).x(); - vertices[k + 7] = mesh3ds.getTexCoord(triVert[j]).y(); - } - } - } - - // clean up - if (faceNormals != NULL) - delete[] faceNormals; - if (vertexNormals != NULL) - delete[] vertexNormals; - if (faceCounts != NULL) - delete[] faceCounts; - if (vertexFaces != NULL) - { - for (i = 0; i < nVertices; i++) - { - if (vertexFaces[i] != NULL) - delete[] vertexFaces[i]; - } - delete[] vertexFaces; - } - Mesh::VertexAttribute attributes[8]; uint32 nAttributes = 0; @@ -237,11 +92,6 @@ Convert3DSMesh(Model& model, nAttributes++; offset += 12; - // Normal attribute is always present - attributes[nAttributes] = Mesh::VertexAttribute(Mesh::Normal, Mesh::Float3, offset); - nAttributes++; - offset += 12; - if (hasTexCoords) { attributes[nAttributes] = Mesh::VertexAttribute(Mesh::Texture0, Mesh::Float2, offset); @@ -252,7 +102,7 @@ Convert3DSMesh(Model& model, // Create the Celestia mesh Mesh* mesh = new Mesh(); mesh->setVertexDescription(Mesh::VertexDescription(offset, nAttributes, attributes)); - mesh->setVertices(nOutputVertices, vertices); + mesh->setVertices(nVertices, vertices); mesh->setName(meshName); @@ -265,9 +115,11 @@ Convert3DSMesh(Model& model, for (unsigned int i = 0; i < faceCount; i++) { - indices[i * 3 + 0] = i * 3 + 0; - indices[i * 3 + 1] = i * 3 + 1; - indices[i * 3 + 2] = i * 3 + 2; + uint16 v0 = 0, v1 = 0, v2 = 0; + mesh3ds.getFace(i, v0, v1, v2); + indices[i * 3 + 0] = v0; + indices[i * 3 + 1] = v1; + indices[i * 3 + 2] = v2; } mesh->addGroup(Mesh::TriList, ~0, faceCount * 3, indices); @@ -280,21 +132,20 @@ Convert3DSMesh(Model& model, { M3DMeshMaterialGroup* matGroup = mesh3ds.getMeshMaterialGroup(groupIndex); - // Vertex lists are not indexed, so the conversion to an indexed format is - // trivial (although much space is wasted storing unnecessary indices.) uint32 nMatGroupFaces = matGroup->faces.size(); - uint32 nMatGroupVertices = nMatGroupFaces * 3; - uint32* indices = new uint32[nMatGroupVertices]; + uint32* indices = new uint32[nMatGroupFaces * 3]; for (unsigned int i = 0; i < nMatGroupFaces; i++) { + uint16 v0 = 0, v1 = 0, v2 = 0; uint16 faceIndex = matGroup->faces[i]; - indices[i * 3 + 0] = faceIndex * 3 + 0; - indices[i * 3 + 1] = faceIndex * 3 + 1; - indices[i * 3 + 2] = faceIndex * 3 + 2; + mesh3ds.getFace(faceIndex, v0, v1, v2); + indices[i * 3 + 0] = v0; + indices[i * 3 + 1] = v1; + indices[i * 3 + 2] = v2; } - // Convert the 3DS mesh's material + // Get the material index unsigned int materialIndex = ~0u; string material3dsName = matGroup->materialName; if (!material3dsName.empty()) @@ -309,7 +160,7 @@ Convert3DSMesh(Model& model, } } - mesh->addGroup(Mesh::TriList, materialIndex, nMatGroupVertices, indices); + mesh->addGroup(Mesh::TriList, materialIndex, nMatGroupFaces * 3, indices); } } diff --git a/src/tools/cmod/mainwindow.cpp b/src/tools/cmod/mainwindow.cpp index bd7e0d5f0..0412a0d58 100644 --- a/src/tools/cmod/mainwindow.cpp +++ b/src/tools/cmod/mainwindow.cpp @@ -59,9 +59,12 @@ MainWindow::MainWindow() : QAction* wireFrameStyleAction = new QAction(tr("&Wireframe"), styleGroup); wireFrameStyleAction->setCheckable(true); wireFrameStyleAction->setData((int) ModelViewWidget::WireFrameStyle); + QAction* backgroundColorAction = new QAction(tr("&Background Color..."), this); styleMenu->addAction(normalStyleAction); styleMenu->addAction(wireFrameStyleAction); + styleMenu->addSeparator(); + styleMenu->addAction(backgroundColorAction); menuBar->addMenu(styleMenu); QMenu* operationsMenu = new QMenu(tr("&Operations")); @@ -97,6 +100,7 @@ MainWindow::MainWindow() : // Connect Style menu connect(styleGroup, SIGNAL(triggered(QAction*)), this, SLOT(setRenderStyle(QAction*))); + connect(backgroundColorAction, SIGNAL(triggered()), this, SLOT(editBackgroundColor())); // Connect Operations menu connect(generateNormalsAction, SIGNAL(triggered()), this, SLOT(generateNormals())); @@ -105,6 +109,33 @@ MainWindow::MainWindow() : connect(mergeMeshesAction, SIGNAL(triggered()), this, SLOT(mergeMeshes())); editMaterialAction->setShortcut(QKeySequence("Ctrl+E")); connect(editMaterialAction, SIGNAL(triggered()), this, SLOT(editMaterial())); + + // Apply settings + QSettings settings; + QColor backgroundColor = settings.value("BackgroundColor", QColor(0, 0, 128)).value(); + m_modelView->setBackgroundColor(backgroundColor); +} + + +static Material* +cloneMaterial(const Material* other) +{ + Material* material = new Material(); + material->diffuse = other->diffuse; + material->specular = other->specular; + material->emissive = other->emissive; + material->specularPower = other->specularPower; + material->opacity = other->opacity; + material->blend = other->blend; + for (int i = 0; i < Material::TextureSemanticMax; ++i) + { + if (other->maps[i]) + { + material->maps[i] = new Material::DefaultTextureResource(other->maps[i]->source()); + } + } + + return material; } @@ -273,7 +304,29 @@ MainWindow::openModel(const QString& fileName) delete scene; - setModel(fileName, model); + // Generate normals for the model + double smoothAngle = 45.0; // degrees + double weldTolerance = 1.0e-6; + bool weldVertices = true; + + Model* newModel = GenerateModelNormals(*model, float(smoothAngle * 3.14159265 / 180.0), weldVertices, weldTolerance); + delete model; + + if (!newModel) + { + QMessageBox::warning(this, tr("Mesh Load Error"), tr("Internal error when loading mesh")); + } + else + { + // Automatically uniquify vertices + for (unsigned int i = 0; newModel->getMesh(i) != NULL; i++) + { + Mesh* mesh = newModel->getMesh(i); + UniquifyVertices(*mesh); + } + + setModel(fileName, newModel); + } } else if (info.suffix().toLower() == "cmod") { @@ -367,28 +420,6 @@ MainWindow::setRenderStyle(QAction* action) } -static Material* -cloneMaterial(const Material* other) -{ - Material* material = new Material(); - material->diffuse = other->diffuse; - material->specular = other->specular; - material->emissive = other->emissive; - material->specularPower = other->specularPower; - material->opacity = other->opacity; - material->blend = other->blend; - for (int i = 0; i < Material::TextureSemanticMax; ++i) - { - if (other->maps[i]) - { - material->maps[i] = new Material::DefaultTextureResource(other->maps[i]->source()); - } - } - - return material; -} - - void MainWindow::generateNormals() { @@ -436,33 +467,16 @@ MainWindow::generateNormals() double weldTolerance = toleranceEdit->text().toDouble(); bool weldVertices = true; - Model* newModel = new Model(); - - // Copy materials - for (unsigned int i = 0; model->getMaterial(i) != NULL; i++) + Model* newModel = GenerateModelNormals(*model, float(smoothAngle * 3.14159265 / 180.0), weldVertices, weldTolerance); + if (!newModel) { - newModel->addMaterial(cloneMaterial(model->getMaterial(i))); + QMessageBox::warning(this, tr("Internal Error"), tr("Out of memory error during normal generation")); } - - for (unsigned int i = 0; model->getMesh(i) != NULL; i++) + else { - Mesh* mesh = model->getMesh(i); - Mesh* newMesh = NULL; - - newMesh = GenerateNormals(*mesh, float(smoothAngle * 3.14159265 / 180.0), weldVertices, weldTolerance); - if (newMesh == NULL) - { - cerr << "Error generating normals!\n"; - // TODO: Clone the mesh and add it to the model - } - else - { - newModel->addMesh(newMesh); - } + setModel(modelFileName(), newModel); } - setModel(modelFileName(), newModel); - settings.setValue("SmoothAngle", smoothAngle); settings.setValue("WeldTolerance", weldTolerance); } @@ -615,3 +629,21 @@ MainWindow::changeCurrentMaterial(const Material& material) m_modelView->setMaterial(selectedGroup->materialIndex, material); } } + + +void +MainWindow::editBackgroundColor() +{ + QColor originalColor = m_modelView->backgroundColor(); + QColorDialog dialog(originalColor, this); + connect(&dialog, SIGNAL(currentColorChanged(QColor)), m_modelView, SLOT(setBackgroundColor(QColor))); + if (dialog.exec() == QDialog::Accepted) + { + QSettings settings; + settings.setValue("BackgroundColor", m_modelView->backgroundColor()); + } + else + { + m_modelView->setBackgroundColor(originalColor); + } +} diff --git a/src/tools/cmod/mainwindow.h b/src/tools/cmod/mainwindow.h index a83e76481..4eb00808f 100644 --- a/src/tools/cmod/mainwindow.h +++ b/src/tools/cmod/mainwindow.h @@ -53,6 +53,8 @@ public slots: void changeCurrentMaterial(const cmod::Material&); + void editBackgroundColor(); + private: ModelViewWidget* m_modelView; QLabel* m_statusBarLabel; diff --git a/src/tools/cmod/modelviewwidget.cpp b/src/tools/cmod/modelviewwidget.cpp index b94ce3122..936215ca4 100644 --- a/src/tools/cmod/modelviewwidget.cpp +++ b/src/tools/cmod/modelviewwidget.cpp @@ -311,6 +311,14 @@ ModelViewWidget::setMaterial(unsigned int index, const cmod::Material& material) update(); } +void +ModelViewWidget::setBackgroundColor(const QColor& color) +{ + m_backgroundColor = color; + update(); +} + + void ModelViewWidget::initializeGL() { @@ -320,7 +328,7 @@ ModelViewWidget::initializeGL() void ModelViewWidget::paintGL() { - glClearColor(0.0f, 0.0f, 0.5f, 0.0f); + glClearColor(m_backgroundColor.redF(), m_backgroundColor.greenF(), m_backgroundColor.blueF(), 0.0f); glDepthMask(GL_TRUE); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); diff --git a/src/tools/cmod/modelviewwidget.h b/src/tools/cmod/modelviewwidget.h index 9310ccbf4..7b1ee1179 100644 --- a/src/tools/cmod/modelviewwidget.h +++ b/src/tools/cmod/modelviewwidget.h @@ -60,10 +60,18 @@ public: return m_renderStyle; } + QColor backgroundColor() const + { + return m_backgroundColor; + } + Eigen::Transform3d cameraTransform() const; void setMaterial(unsigned int index, const cmod::Material& material); +public slots: + void setBackgroundColor(const QColor& color); + protected: void initializeGL(); void paintGL(); @@ -86,6 +94,8 @@ private: MaterialLibrary* m_materialLibrary; QSet m_selection; + + QColor m_backgroundColor; }; #endif // _CMODVIEW_MODEL_VIEW_WIDGET_H_