Added new tool for viewing, converting, and manipulating 3DS and CMOD files.
parent
d539fb575b
commit
14fa908e52
|
@ -0,0 +1,36 @@
|
|||
// cmoddview - An application for previewing cmod and other 3D file formats
|
||||
// supported by Celestia.
|
||||
//
|
||||
// Copyright (C) 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.
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGLFormat>
|
||||
#include "mainwindow.h"
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QCoreApplication::setOrganizationName("Celestia");
|
||||
QCoreApplication::setOrganizationDomain("shatters.net");
|
||||
QCoreApplication::setApplicationName("cmodview");
|
||||
|
||||
// Enable multisample antialiasing
|
||||
QGLFormat format;
|
||||
format.setSampleBuffers(true);
|
||||
format.setSamples(4);
|
||||
QGLFormat::setDefaultFormat(format);
|
||||
|
||||
MainWindow window;
|
||||
window.resize(QSize(800, 600));
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
TEMPLATE = app
|
||||
TARGET = cmodview
|
||||
|
||||
QT += opengl
|
||||
|
||||
DESTDIR = build
|
||||
OBJECTS_DIR = build
|
||||
MOC_DIR = build
|
||||
|
||||
HEADERS = \
|
||||
mainwindow.h \
|
||||
modelviewwidget.h \
|
||||
convert3ds.h
|
||||
|
||||
SOURCES = \
|
||||
mainwindow.cpp \
|
||||
cmodview.cpp \
|
||||
modelviewwidget.cpp \
|
||||
convert3ds.cpp
|
||||
|
||||
|
||||
#### CMOD Mesh library ####
|
||||
|
||||
MODEL_SOURCES = \
|
||||
../../celmodel/material.cpp \
|
||||
../../celmodel/mesh.cpp \
|
||||
../../celmodel/model.cpp \
|
||||
../../celmodel/modelfile.cpp
|
||||
|
||||
MODEL_HEADERS = \
|
||||
../../celmodel/material.h \
|
||||
../../celmodel/mesh.h \
|
||||
../../celmodel/model.h \
|
||||
../../celmodel/modelfile.h
|
||||
|
||||
|
||||
#### 3DS Mesh library ####
|
||||
|
||||
TDS_SOURCES = \
|
||||
../../cel3ds/3dsmodel.cpp \
|
||||
../../cel3ds/3dsread.cpp
|
||||
|
||||
TDS_HEADERS = \
|
||||
../../cel3ds/3dschunk.h \
|
||||
../../cel3ds/3dsmodel.h \
|
||||
../../cel3ds/3dsread.h
|
||||
|
||||
SOURCES += $$MODEL_SOURCES $$TDS_SOURCES
|
||||
HEADERS += $$MODEL_HEADERS $$TDS_HEADERS
|
||||
|
||||
INCLUDEPATH += ../..
|
||||
INCLUDEPATH += ../../../thirdparty/Eigen
|
||||
INCLUDEPATH += ../../../thirdparty/glew/include
|
||||
|
||||
macx {
|
||||
DEFINES += TARGET_OS_MAC
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
// convert3ds.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 3DS scene into a Celestia model (cmod)
|
||||
|
||||
#include "convert3ds.h"
|
||||
#include <Eigen/Core>
|
||||
|
||||
using namespace cmod;
|
||||
using namespace Eigen;
|
||||
using namespace std;
|
||||
|
||||
|
||||
void
|
||||
Convert3DSMesh(Model& model,
|
||||
M3DTriangleMesh& mesh3ds,
|
||||
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++)
|
||||
{
|
||||
faceCounts[i] = 0;
|
||||
vertexFaces[i] = NULL;
|
||||
}
|
||||
|
||||
// generate face normals
|
||||
for (i = 0; i < nFaces; i++)
|
||||
{
|
||||
uint16 v0, v1, v2;
|
||||
mesh3ds.getFace(i, v0, v1, v2);
|
||||
|
||||
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++)
|
||||
{
|
||||
vertexNormals[i * 3] = faceNormals[i];
|
||||
vertexNormals[i * 3 + 1] = faceNormals[i];
|
||||
vertexNormals[i * 3 + 2] = faceNormals[i];
|
||||
}
|
||||
}
|
||||
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;
|
||||
uint32 offset = 0;
|
||||
|
||||
// Position attribute is always present
|
||||
attributes[nAttributes] = Mesh::VertexAttribute(Mesh::Position, Mesh::Float3, 0);
|
||||
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);
|
||||
nAttributes++;
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
// Create the Celestia mesh
|
||||
Mesh* mesh = new Mesh();
|
||||
mesh->setVertexDescription(Mesh::VertexDescription(offset, nAttributes, attributes));
|
||||
mesh->setVertices(nOutputVertices, vertices);
|
||||
|
||||
mesh->setName(meshName);
|
||||
|
||||
for (uint32 groupIndex = 0; groupIndex < mesh3ds.getMeshMaterialGroupCount(); ++groupIndex)
|
||||
{
|
||||
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];
|
||||
|
||||
for (unsigned int i = 0; i < nMatGroupFaces; i++)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// Convert the 3DS mesh's material
|
||||
Material* material = new Material();
|
||||
|
||||
string material3dsName = matGroup->materialName;
|
||||
if (material3dsName.length() > 0)
|
||||
{
|
||||
int nMaterials = scene.getMaterialCount();
|
||||
for (int i = 0; i < nMaterials; i++)
|
||||
{
|
||||
M3DMaterial* material3ds = scene.getMaterial(i);
|
||||
|
||||
if (material3dsName == material3ds->getName())
|
||||
{
|
||||
M3DColor diffuse = material3ds->getDiffuseColor();
|
||||
material->diffuse = Material::Color(diffuse.red, diffuse.green, diffuse.blue);
|
||||
M3DColor specular = material3ds->getSpecularColor();
|
||||
material->specular = Material::Color(specular.red, specular.green, specular.blue);
|
||||
// Map the shininess from the 3DS file into the 0-128
|
||||
// range that OpenGL uses for the specular exponent.
|
||||
float specPow = (float) pow(2.0, 1.0 + 0.1 * material3ds->getShininess());
|
||||
if (specPow > 128.0f)
|
||||
{
|
||||
specPow = 128.0f;
|
||||
}
|
||||
material->specularPower = specPow;
|
||||
|
||||
material->opacity = material3ds->getOpacity();
|
||||
if (material3ds->getTextureMap() != "")
|
||||
{
|
||||
material->maps[Material::DiffuseMap] = new Material::DefaultTextureResource(material3ds->getTextureMap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32 materialIndex = model.addMaterial(material) - 1;
|
||||
mesh->addGroup(Mesh::TriList, materialIndex, nMatGroupVertices, indices);
|
||||
}
|
||||
}
|
||||
|
||||
model.addMesh(mesh);
|
||||
}
|
||||
|
||||
|
||||
Model*
|
||||
Convert3DSModel(const M3DScene& scene)
|
||||
{
|
||||
Model* model = new Model();
|
||||
|
||||
for (unsigned int i = 0; i < scene.getModelCount(); i++)
|
||||
{
|
||||
M3DModel* model3ds = scene.getModel(i);
|
||||
if (model3ds != NULL)
|
||||
{
|
||||
for (unsigned int j = 0; j < model3ds->getTriMeshCount(); j++)
|
||||
{
|
||||
M3DTriangleMesh* mesh = model3ds->getTriMesh(j);
|
||||
|
||||
if (mesh != NULL && mesh->getFaceCount() > 0)
|
||||
{
|
||||
Convert3DSMesh(*model, *mesh, scene, model3ds->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
#if 0
|
||||
// Sort the vertex lists to make sure that the transparent ones are
|
||||
// rendered after the opaque ones and material state changes are minimized.
|
||||
sort(vertexLists.begin(), vertexLists.end(), compareVertexLists);
|
||||
#endif
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// convert3ds.h
|
||||
//
|
||||
// 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 3DS scene into a Celestia model (cmod)
|
||||
|
||||
#ifndef _CONVERT3DS_H_
|
||||
#define _CONVERT3DS_H_
|
||||
|
||||
#include <celmodel/model.h>
|
||||
#include <cel3ds/3dsmodel.h>
|
||||
|
||||
extern void Convert3DSMesh(cmod::Model& model,
|
||||
M3DTriangleMesh& mesh3ds,
|
||||
const M3DScene& scene,
|
||||
const std::string& meshName);
|
||||
extern cmod::Model* Convert3DSModel(const M3DScene& scene);
|
||||
|
||||
#endif // _CONVERT3DS_H_
|
|
@ -0,0 +1,237 @@
|
|||
// cmodview - a Qt-based application for viewing CMOD and
|
||||
// other Celestia-compatible mesh files.
|
||||
//
|
||||
// Copyright (C) 2009, 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.
|
||||
|
||||
#include <QtGui>
|
||||
#include "mainwindow.h"
|
||||
#include "convert3ds.h"
|
||||
#include <cel3ds/3dsread.h>
|
||||
#include <celmodel/modelfile.h>
|
||||
|
||||
using namespace cmod;
|
||||
using namespace std;
|
||||
|
||||
|
||||
MainWindow::MainWindow() :
|
||||
m_modelView(NULL),
|
||||
m_saveAction(NULL),
|
||||
m_saveAsAction(NULL)
|
||||
{
|
||||
m_modelView = new ModelViewWidget(this);
|
||||
|
||||
setCentralWidget(m_modelView);
|
||||
setWindowTitle("cmodview");
|
||||
|
||||
QMenuBar* menuBar = new QMenuBar(this);
|
||||
|
||||
QMenu* fileMenu = new QMenu(tr("&File"));
|
||||
QAction* openAction = new QAction(tr("&Open..."), this);
|
||||
m_saveAction = new QAction(tr("&Save"), this);
|
||||
m_saveAsAction = new QAction(tr("Save As..."), this);
|
||||
QAction* quitAction = new QAction(tr("&Quit"), this);
|
||||
|
||||
fileMenu->addAction(openAction);
|
||||
fileMenu->addAction(m_saveAction);
|
||||
fileMenu->addAction(m_saveAsAction);
|
||||
fileMenu->addSeparator();
|
||||
fileMenu->addAction(quitAction);
|
||||
menuBar->addMenu(fileMenu);
|
||||
|
||||
QMenu* styleMenu = new QMenu(tr("&Render Style"));
|
||||
QActionGroup* styleGroup = new QActionGroup(styleMenu);
|
||||
QAction* normalStyleAction = new QAction(tr("&Normal"), styleGroup);
|
||||
normalStyleAction->setCheckable(true);
|
||||
normalStyleAction->setChecked(true);
|
||||
normalStyleAction->setData((int) ModelViewWidget::NormalStyle);
|
||||
QAction* wireFrameStyleAction = new QAction(tr("&Wireframe"), styleGroup);
|
||||
wireFrameStyleAction->setCheckable(true);
|
||||
wireFrameStyleAction->setData((int) ModelViewWidget::WireFrameStyle);
|
||||
|
||||
styleMenu->addAction(normalStyleAction);
|
||||
styleMenu->addAction(wireFrameStyleAction);
|
||||
menuBar->addMenu(styleMenu);
|
||||
|
||||
setMenuBar(menuBar);
|
||||
|
||||
m_saveAction->setEnabled(false);
|
||||
m_saveAsAction->setEnabled(false);
|
||||
|
||||
openAction->setShortcut(QKeySequence::Open);
|
||||
connect(openAction, SIGNAL(triggered()), this, SLOT(openModel()));
|
||||
m_saveAction->setShortcut(QKeySequence::Save);
|
||||
connect(m_saveAction, SIGNAL(triggered()), this, SLOT(saveModel()));
|
||||
m_saveAsAction->setShortcut(QKeySequence::SaveAs);
|
||||
connect(m_saveAsAction, SIGNAL(triggered()), this, SLOT(saveModelAs()));
|
||||
quitAction->setShortcut(QKeySequence("Ctrl+Q"));
|
||||
connect(quitAction, SIGNAL(triggered()), this, SLOT(close()));
|
||||
|
||||
connect(styleGroup, SIGNAL(triggered(QAction*)), this, SLOT(setRenderStyle(QAction*)));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MainWindow::setModel(const QString& fileName, Model* model)
|
||||
{
|
||||
m_modelView->setModel(model);
|
||||
setModelFileName(fileName);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MainWindow::setModelFileName(const QString& fileName)
|
||||
{
|
||||
m_modelFileName = fileName;
|
||||
|
||||
QFileInfo info(fileName);
|
||||
setWindowTitle(QString("cmodview - %1").arg(info.fileName()));
|
||||
|
||||
if (fileName.isEmpty())
|
||||
{
|
||||
m_saveAction->setDisabled(true);
|
||||
m_saveAsAction->setDisabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_saveAction->setEnabled(exportSupported(fileName));
|
||||
m_saveAsAction->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
MainWindow::exportSupported(const QString& fileName) const
|
||||
{
|
||||
QString ext = QFileInfo(fileName).suffix().toLower();
|
||||
|
||||
return ext == "cmod";
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::openModel()
|
||||
{
|
||||
QSettings settings;
|
||||
QString openFileDir = settings.value("OpenModelDir", QDir::homePath()).toString();
|
||||
|
||||
QString fileName = QFileDialog::getOpenFileName(this,
|
||||
tr("Open Model File"),
|
||||
openFileDir,
|
||||
tr("Model and mesh files (*.cmod *.3ds)"));
|
||||
|
||||
if (!fileName.isEmpty())
|
||||
{
|
||||
string fileNameStd = string(fileName.toUtf8().data());
|
||||
|
||||
QFileInfo info(fileName);
|
||||
|
||||
settings.setValue("OpenModelDir", info.absolutePath());
|
||||
|
||||
if (info.suffix().toLower() == "3ds")
|
||||
{
|
||||
M3DScene* scene = Read3DSFile(fileNameStd);
|
||||
if (scene == NULL)
|
||||
{
|
||||
QMessageBox::warning(this, "Load error", tr("Error reading 3DS file %1").arg(fileName));
|
||||
return;
|
||||
}
|
||||
|
||||
Model* model = Convert3DSModel(*scene);
|
||||
if (model == NULL)
|
||||
{
|
||||
QMessageBox::warning(this, "Load error", tr("Internal error converting 3DS file %1").arg(fileName));
|
||||
return;
|
||||
}
|
||||
|
||||
delete scene;
|
||||
|
||||
setModel(fileName, model);
|
||||
}
|
||||
else if (info.suffix().toLower() == "cmod")
|
||||
{
|
||||
Model* model = NULL;
|
||||
ifstream in(fileNameStd.c_str(), ios::in | ios::binary);
|
||||
if (!in.good())
|
||||
{
|
||||
QMessageBox::warning(this, "Load error", tr("Error opening CMOD file %1").arg(fileName));
|
||||
return;
|
||||
}
|
||||
|
||||
model = LoadModel(in);
|
||||
if (model == NULL)
|
||||
{
|
||||
QMessageBox::warning(this, "Load error", tr("Error reading CMOD file %1").arg(fileName));
|
||||
return;
|
||||
}
|
||||
|
||||
setModel(fileName, model);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shouldn't be allowed by QFileDialog::getOpenFileName()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MainWindow::saveModel()
|
||||
{
|
||||
if (exportSupported(modelFileName()))
|
||||
{
|
||||
saveModel(modelFileName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MainWindow::saveModelAs()
|
||||
{
|
||||
QString saveFileName = QFileDialog::getSaveFileName(this, tr("Save model as..."), "", tr("CMOD files (*.cmod)"));
|
||||
if (!saveFileName.isEmpty())
|
||||
{
|
||||
saveModel(saveFileName);
|
||||
setModelFileName(saveFileName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MainWindow::saveModel(const QString& saveFileName)
|
||||
{
|
||||
string fileNameStd = string(saveFileName.toUtf8().data());
|
||||
|
||||
ofstream out(fileNameStd.c_str(), ios::out | ios::binary);
|
||||
bool ok = false;
|
||||
if (out.good())
|
||||
{
|
||||
ok = SaveModelBinary(m_modelView->model(), out);
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
QMessageBox::warning(this, "Save error", tr("Error writing to file %1").arg(saveFileName));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MainWindow::setRenderStyle(QAction* action)
|
||||
{
|
||||
cout << "setRenderStyle\n";
|
||||
ModelViewWidget::RenderStyle renderStyle = (ModelViewWidget::RenderStyle) action->data().toInt();
|
||||
switch (renderStyle)
|
||||
{
|
||||
case ModelViewWidget::NormalStyle:
|
||||
case ModelViewWidget::WireFrameStyle:
|
||||
m_modelView->setRenderStyle(renderStyle);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// qttxf - a Qt-based application to generate GLUT txf files from
|
||||
// system fonts
|
||||
//
|
||||
// Copyright (C) 2009, 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.
|
||||
|
||||
#ifndef _CMODVIEW_MAINWINDOW_H_
|
||||
#define _CMODVIEW_MAINWINDOW_H_
|
||||
|
||||
#include "modelviewwidget.h"
|
||||
#include <QMainWindow>
|
||||
#include <QString>
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainWindow();
|
||||
void setModel(const QString& filename, cmod::Model* model);
|
||||
void setModelFileName(const QString& fileName);
|
||||
QString modelFileName() const
|
||||
{
|
||||
return m_modelFileName;
|
||||
}
|
||||
|
||||
bool exportSupported(const QString& fileName) const;
|
||||
|
||||
public slots:
|
||||
void openModel();
|
||||
void saveModel();
|
||||
void saveModelAs();
|
||||
void saveModel(const QString& saveFileName);
|
||||
void setRenderStyle(QAction* action);
|
||||
|
||||
private:
|
||||
ModelViewWidget* m_modelView;
|
||||
QString m_modelFileName;
|
||||
QAction* m_saveAction;
|
||||
QAction* m_saveAsAction;
|
||||
};
|
||||
|
||||
#endif // _CMODVIEW_MAINWINDOW_H_
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
// cmoddview - An application for previewing cmod and other 3D file formats
|
||||
// supported by Celestia.
|
||||
//
|
||||
// Copyright (C) 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.
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include "modelviewwidget.h"
|
||||
#include <QtOpenGL>
|
||||
|
||||
using namespace cmod;
|
||||
using namespace Eigen;
|
||||
|
||||
|
||||
ModelViewWidget::ModelViewWidget(QWidget *parent) :
|
||||
QGLWidget(parent),
|
||||
m_model(NULL),
|
||||
m_modelBoundingRadius(1.0),
|
||||
m_cameraPosition(Vector3d::Zero()),
|
||||
m_cameraOrientation(Quaterniond::Identity()),
|
||||
m_renderStyle(NormalStyle)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ModelViewWidget::~ModelViewWidget()
|
||||
{
|
||||
delete m_model;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ModelViewWidget::setModel(cmod::Model* model)
|
||||
{
|
||||
if (m_model && m_model != model)
|
||||
{
|
||||
delete m_model;
|
||||
}
|
||||
m_model = model;
|
||||
|
||||
AlignedBox<float, 3> bbox;
|
||||
if (m_model != NULL)
|
||||
{
|
||||
for (unsigned int i = 0; i < m_model->getMeshCount(); ++i)
|
||||
{
|
||||
bbox.extend(m_model->getMesh(i)->getBoundingBox());
|
||||
}
|
||||
}
|
||||
|
||||
m_modelBoundingRadius = bbox.max().norm();
|
||||
m_cameraPosition = m_modelBoundingRadius * Vector3d::UnitZ();
|
||||
m_cameraOrientation = Quaterniond::Identity();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ModelViewWidget::setRenderStyle(RenderStyle style)
|
||||
{
|
||||
if (style != m_renderStyle)
|
||||
{
|
||||
m_renderStyle = style;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ModelViewWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
m_lastMousePosition = event->pos();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ModelViewWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
int dx = event->x() - m_lastMousePosition.x();
|
||||
int dy = event->y() - m_lastMousePosition.y();
|
||||
|
||||
if (event->buttons() & Qt::LeftButton)
|
||||
{
|
||||
double xrotation = (double) dy / 100.0;
|
||||
double yrotation = (double) dx / 100.0;
|
||||
Quaterniond q = AngleAxis<double>(-xrotation, Vector3d::UnitX()) *
|
||||
AngleAxis<double>(-yrotation, Vector3d::UnitY());
|
||||
|
||||
Quaterniond r = m_cameraOrientation * q * m_cameraOrientation.conjugate();
|
||||
r.normalize(); // guard against accumulating rounding errors
|
||||
|
||||
m_cameraPosition = r * m_cameraPosition;
|
||||
m_cameraOrientation = r * m_cameraOrientation;
|
||||
}
|
||||
|
||||
m_lastMousePosition = event->pos();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ModelViewWidget::wheelEvent(QWheelEvent* event)
|
||||
{
|
||||
if (event->orientation() != Qt::Vertical)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Mouse wheel controls camera dolly
|
||||
double adjust = m_modelBoundingRadius * event->delta() / 1000.0;
|
||||
double newDistance = m_cameraPosition.norm() + adjust;
|
||||
m_cameraPosition = m_cameraPosition.normalized() * newDistance;
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ModelViewWidget::initializeGL()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ModelViewWidget::paintGL()
|
||||
{
|
||||
glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
double aspectRatio = (double) size().width() / (double) size().height();
|
||||
double nearDistance = m_modelBoundingRadius * 0.05;
|
||||
double farDistance = m_modelBoundingRadius * 20.0;
|
||||
gluPerspective(45.0, aspectRatio, nearDistance, farDistance);
|
||||
|
||||
glEnable(GL_LIGHTING);
|
||||
|
||||
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
|
||||
Vector4f ambientLight(0.2f, 0.2f, 0.2f, 1.0f);
|
||||
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientLight.data());
|
||||
glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL_EXT, GL_SEPARATE_SPECULAR_COLOR_EXT);
|
||||
|
||||
glEnable(GL_LIGHT0);
|
||||
Vector4f lightDir0(100.0f, 100.0f, 500.0f, 0.0f);
|
||||
Vector4f lightDiffuse0(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glLightfv(GL_LIGHT0, GL_POSITION, lightDir0.data());
|
||||
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse0.data());
|
||||
glLightfv(GL_LIGHT0, GL_SPECULAR, lightDiffuse0.data());
|
||||
|
||||
glEnable(GL_LIGHT1);
|
||||
Vector4f lightDir1(300.0f, -300.0f, -100.0f, 0.0f);
|
||||
Vector4f lightDiffuse1(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glLightfv(GL_LIGHT1, GL_POSITION, lightDir1.data());
|
||||
glLightfv(GL_LIGHT1, GL_DIFFUSE, lightDiffuse1.data());
|
||||
glLightfv(GL_LIGHT1, GL_SPECULAR, lightDiffuse1.data());
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
Transform3d cameraRotation(m_cameraOrientation.conjugate());
|
||||
glMultMatrixd(cameraRotation.data());
|
||||
glTranslated(-m_cameraPosition.x(), -m_cameraPosition.y(), -m_cameraPosition.z());
|
||||
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthMask(GL_TRUE);
|
||||
|
||||
if (m_model)
|
||||
{
|
||||
renderModel(m_model);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ModelViewWidget::resizeGL(int width, int height)
|
||||
{
|
||||
glViewport(0, 0, width, height);
|
||||
}
|
||||
|
||||
|
||||
static GLenum GLComponentTypes[Mesh::FormatMax] =
|
||||
{
|
||||
GL_FLOAT, // Float1
|
||||
GL_FLOAT, // Float2
|
||||
GL_FLOAT, // Float3
|
||||
GL_FLOAT, // Float4,
|
||||
GL_UNSIGNED_BYTE, // UByte4
|
||||
};
|
||||
|
||||
static int GLComponentCounts[Mesh::FormatMax] =
|
||||
{
|
||||
1, // Float1
|
||||
2, // Float2
|
||||
3, // Float3
|
||||
4, // Float4,
|
||||
4, // UByte4
|
||||
};
|
||||
|
||||
static void
|
||||
setVertexArrays(const Mesh::VertexDescription& desc, const void* vertexData)
|
||||
{
|
||||
const Mesh::VertexAttribute& position = desc.getAttribute(Mesh::Position);
|
||||
const Mesh::VertexAttribute& normal = desc.getAttribute(Mesh::Normal);
|
||||
const Mesh::VertexAttribute& color0 = desc.getAttribute(Mesh::Color0);
|
||||
const Mesh::VertexAttribute& texCoord0 = desc.getAttribute(Mesh::Texture0);
|
||||
|
||||
// Can't render anything unless we have positions
|
||||
if (position.format != Mesh::Float3)
|
||||
return;
|
||||
|
||||
// Set up the vertex arrays
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer(3, GL_FLOAT, desc.stride,
|
||||
reinterpret_cast<const char*>(vertexData) + position.offset);
|
||||
|
||||
// Set up the normal array
|
||||
switch (normal.format)
|
||||
{
|
||||
case Mesh::Float3:
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
glNormalPointer(GLComponentTypes[(int) normal.format],
|
||||
desc.stride,
|
||||
reinterpret_cast<const char*>(vertexData) + normal.offset);
|
||||
break;
|
||||
default:
|
||||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up the color array
|
||||
switch (color0.format)
|
||||
{
|
||||
case Mesh::Float3:
|
||||
case Mesh::Float4:
|
||||
case Mesh::UByte4:
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glColorPointer(GLComponentCounts[color0.format],
|
||||
GLComponentTypes[color0.format],
|
||||
desc.stride,
|
||||
reinterpret_cast<const char*>(vertexData) + color0.offset);
|
||||
break;
|
||||
default:
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set up the texture coordinate array
|
||||
switch (texCoord0.format)
|
||||
{
|
||||
case Mesh::Float1:
|
||||
case Mesh::Float2:
|
||||
case Mesh::Float3:
|
||||
case Mesh::Float4:
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glTexCoordPointer(GLComponentCounts[(int) texCoord0.format],
|
||||
GLComponentTypes[(int) texCoord0.format],
|
||||
desc.stride,
|
||||
reinterpret_cast<const char*>(vertexData) + texCoord0.offset);
|
||||
break;
|
||||
default:
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ModelViewWidget::renderModel(Model* model)
|
||||
{
|
||||
glEnable(GL_CULL_FACE);
|
||||
if (m_renderStyle == WireFrameStyle)
|
||||
{
|
||||
glPolygonMode(GL_FRONT, GL_LINE);
|
||||
}
|
||||
else
|
||||
{
|
||||
glPolygonMode(GL_FRONT, GL_FILL);
|
||||
}
|
||||
|
||||
for (unsigned int meshIndex = 0; meshIndex < model->getMeshCount(); ++meshIndex)
|
||||
{
|
||||
const Mesh* mesh = model->getMesh(meshIndex);
|
||||
|
||||
setVertexArrays(mesh->getVertexDescription(), mesh->getVertexData());
|
||||
|
||||
for (unsigned int groupIndex = 0; groupIndex < mesh->getGroupCount(); ++groupIndex)
|
||||
{
|
||||
const Mesh::PrimitiveGroup* group = mesh->getGroup(groupIndex);
|
||||
if (group->materialIndex < model->getMaterialCount())
|
||||
{
|
||||
const Material* material = model->getMaterial(group->materialIndex);
|
||||
Vector4f diffuse(material->diffuse.red(), material->diffuse.green(), material->diffuse.blue(), 1.0f);
|
||||
Vector4f specular(material->specular.red(), material->specular.green(), material->specular.blue(), 1.0f);
|
||||
glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse.data());
|
||||
glMaterialfv(GL_FRONT, GL_AMBIENT, diffuse.data());
|
||||
glColor4fv(diffuse.data());
|
||||
glMaterialfv(GL_FRONT, GL_SPECULAR, specular.data());
|
||||
glMaterialfv(GL_FRONT, GL_SHININESS, &material->specularPower);
|
||||
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
GLenum primitiveMode = 0;
|
||||
switch (group->prim)
|
||||
{
|
||||
case Mesh::TriList:
|
||||
primitiveMode = GL_TRIANGLES;
|
||||
break;
|
||||
case Mesh::TriStrip:
|
||||
primitiveMode = GL_TRIANGLE_STRIP;
|
||||
break;
|
||||
case Mesh::TriFan:
|
||||
primitiveMode = GL_TRIANGLE_FAN;
|
||||
break;
|
||||
case Mesh::LineList:
|
||||
primitiveMode = GL_LINES;
|
||||
break;
|
||||
case Mesh::LineStrip:
|
||||
primitiveMode = GL_LINE_STRIP;
|
||||
break;
|
||||
case Mesh::PointList:
|
||||
primitiveMode = GL_POINTS;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (primitiveMode != 0)
|
||||
{
|
||||
glDrawElements(primitiveMode, group->nIndices, GL_UNSIGNED_INT, group->indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// cmoddview - An application for previewing cmod and other 3D file formats
|
||||
// supported by Celestia.
|
||||
//
|
||||
// Copyright (C) 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.
|
||||
|
||||
#ifndef _CMODVIEW_MODEL_VIEW_WIDGET_H_
|
||||
#define _CMODVIEW_MODEL_VIEW_WIDGET_H_
|
||||
|
||||
#include <QGLWidget>
|
||||
#include <celmodel/model.h>
|
||||
#include <Eigen/Core>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
|
||||
class ModelViewWidget : public QGLWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ModelViewWidget(QWidget *parent);
|
||||
~ModelViewWidget();
|
||||
|
||||
void setModel(cmod::Model* model);
|
||||
cmod::Model* model() const
|
||||
{
|
||||
return m_model;
|
||||
}
|
||||
|
||||
enum RenderStyle
|
||||
{
|
||||
NormalStyle,
|
||||
WireFrameStyle,
|
||||
};
|
||||
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
void mouseMoveEvent(QMouseEvent *event);
|
||||
void wheelEvent(QWheelEvent* event);
|
||||
|
||||
void setRenderStyle(RenderStyle style);
|
||||
RenderStyle renderStyle() const
|
||||
{
|
||||
return m_renderStyle;
|
||||
}
|
||||
|
||||
protected:
|
||||
void initializeGL();
|
||||
void paintGL();
|
||||
void resizeGL(int width, int height);
|
||||
|
||||
private:
|
||||
void renderModel(cmod::Model* model);
|
||||
|
||||
private:
|
||||
cmod::Model* m_model;
|
||||
double m_modelBoundingRadius;
|
||||
Eigen::Vector3d m_cameraPosition;
|
||||
Eigen::Quaterniond m_cameraOrientation;
|
||||
QPoint m_lastMousePosition;
|
||||
RenderStyle m_renderStyle;
|
||||
};
|
||||
|
||||
#endif // _CMODVIEW_MODEL_VIEW_WIDGET_H_
|
Loading…
Reference in New Issue