Added new tool for viewing, converting, and manipulating 3DS and CMOD files.

sensor-dev
Chris Laurel 2010-02-03 04:16:22 +00:00
parent d539fb575b
commit 14fa908e52
8 changed files with 1124 additions and 0 deletions

View File

@ -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();
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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_

View File

@ -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;
}
}

View File

@ -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_

View File

@ -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);
}
}
}
}

View File

@ -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_