bgfx/3rdparty/openctm/tools/ctmviewer.cpp
2012-10-07 20:41:18 -07:00

1787 lines
48 KiB
C++

//-----------------------------------------------------------------------------
// Product: OpenCTM tools
// File: ctmviewer.cpp
// Description: 3D file viewer. The program can be used to view OpenCTM files
// in an interactive OpenGL window. Files in other supported
// formats can also be viewed.
//-----------------------------------------------------------------------------
// Copyright (c) 2009-2010 Marcus Geelnard
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//-----------------------------------------------------------------------------
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <GL/glew.h>
#ifdef __APPLE_CC__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <openctm.h>
#include "mesh.h"
#include "meshio.h"
#include "sysdialog.h"
#include "systimer.h"
#include "image.h"
#include "common.h"
using namespace std;
// We need PI
#ifndef PI
#define PI 3.141592653589793238462643f
#endif
// Configuration constants
#define FOCUS_TIME 0.1
#define DOUBLE_CLICK_TIME 0.25
//-----------------------------------------------------------------------------
// GLSL source code (generated from source by bin2c)
//-----------------------------------------------------------------------------
#include "phong_vert.h"
#include "phong_frag.h"
//-----------------------------------------------------------------------------
// Icon bitmaps
//-----------------------------------------------------------------------------
#include "icons/icon_open.h"
#include "icons/icon_save.h"
#include "icons/icon_texture.h"
#include "icons/icon_help.h"
//-----------------------------------------------------------------------------
// The GLViewer application class (declaration)
//-----------------------------------------------------------------------------
class GLButton;
class GLViewer {
private:
// File information for the current mesh
string mFileName, mFilePath;
long mFileSize;
// Window state cariables
int mWidth, mHeight;
GLint mDepthBufferResolution;
int mOldMouseX, mOldMouseY;
double mLastClickTime;
bool mMouseRotate;
bool mMouseZoom;
bool mMousePan;
bool mFocusing;
Vector3 mFocusStartPos;
Vector3 mFocusEndPos;
double mFocusStartTime;
double mFocusEndTime;
double mFocusStartDistance;
double mFocusEndDistance;
// Camera state
Vector3 mCameraPosition;
Vector3 mCameraLookAt;
Vector3 mCameraUp;
GLdouble mModelviewMatrix[16];
GLdouble mProjectionMatrix[16];
GLint mViewport[4];
// Mesh information
Mesh * mMesh;
Vector3 mAABBMin, mAABBMax;
GLuint mDisplayList;
GLuint mTexHandle;
// Polygon rendering mode (fill / line)
GLenum mPolyMode;
// GLSL objects
bool mUseShader;
GLuint mShaderProgram;
GLuint mVertShader;
GLuint mFragShader;
// List of GUI buttons
list<GLButton *> mButtons;
// Master timer resource
SysTimer mTimer;
/// Set up the camera.
void SetupCamera();
/// Initialize the GLSL shader (requires OpenGL 2.0 or better).
void InitShader();
/// Initialize the texture.
void InitTexture(const char * aFileName);
/// Set up the scene lighting.
void SetupLighting();
/// Set up the material.
void SetupMaterial();
/// Draw a mesh
void DrawMesh(Mesh * aMesh);
/// Load a file to the mesh
void LoadFile(const char * aFileName, const char * aOverrideTexture);
/// Load a texture file
void LoadTexture(const char * aFileName);
/// Draw an outline box.
void DrawOutlineBox(int x1, int y1, int x2, int y2,
float r, float g, float b, float a);
/// Draw a string using GLUT. The string is shown on top of an alpha-blended
/// quad.
void DrawString(string aString, int x, int y);
/// Draw 2D overlay
void Draw2DOverlay();
/// Get 3D coordinate under the mouse cursor.
bool WinCoordTo3DCoord(int x, int y, Vector3 &aPoint);
/// Update the focus position of the camera.
void UpdateFocus();
public:
/// Constructor
GLViewer();
/// Destructor
~GLViewer();
/// Open another file
void ActionOpenFile();
/// Save the file
void ActionSaveFile();
/// Open a texture file
void ActionOpenTexture();
/// Toggle wire frame view on/off
void ActionToggleWireframe();
/// Fit model to the screen (re-focus)
void ActionFitToScreen();
/// Set camera up direction to Y
void ActionCameraUpY();
/// Set camera up direction to Z
void ActionCameraUpZ();
/// Zoom camera one step in
void ActionZoomIn();
/// Zoom camera one step out
void ActionZoomOut();
/// Exit program
void ActionExit();
/// Show a help dialog
void ActionHelp();
/// Redraw function.
void WindowRedraw(void);
/// Resize function.
void WindowResize(int w, int h);
/// Mouse click function
void MouseClick(int button, int state, int x, int y);
/// Mouse move function
void MouseMove(int x, int y);
/// Keyboard function
void KeyDown(unsigned char key, int x, int y);
/// Keyboard function (special keys)
void SpecialKeyDown(int key, int x, int y);
/// Run the application
void Run(int argc, char **argv);
};
//-----------------------------------------------------------------------------
// A class for OpenGL rendered GUI buttons
//-----------------------------------------------------------------------------
class GLButton {
private:
// Texture handle
GLuint mTexHandle;
// Highlight on/off
bool mHighlight;
public:
/// Constructor.
GLButton()
{
mTexHandle = 0;
mPosX = 0;
mPosY = 0;
mWidth = 32;
mHeight = 32;
mHighlight = false;
mParent = NULL;
}
/// Destructor.
virtual ~GLButton()
{
if(mTexHandle)
glDeleteTextures(1, &mTexHandle);
}
/// Set glyph for this button.
void SetGlyph(const unsigned char * aBitmap, int aWidth, int aHeight,
int aComponents)
{
// Update the button size
mWidth = aWidth;
mHeight = aHeight;
// Upload the texture to OpenGL
if(mTexHandle)
glDeleteTextures(1, &mTexHandle);
glGenTextures(1, &mTexHandle);
if(mTexHandle)
{
// Determine the color format
GLuint format;
if(aComponents == 3)
format = GL_RGB;
else if(aComponents == 4)
format = GL_RGBA;
else
format = GL_LUMINANCE;
glBindTexture(GL_TEXTURE_2D, mTexHandle);
if(GLEW_VERSION_1_4)
{
// Generate mipmaps automatically and use them
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
else
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, aComponents, aWidth, aHeight, 0,
format, GL_UNSIGNED_BYTE, (GLvoid *) aBitmap);
}
}
/// Redraw function.
void Redraw()
{
// Set opacity of the icon
if(mHighlight)
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
else
glColor4f(1.0f, 1.0f, 1.0f, 0.7f);
// Enable texturing
if(mTexHandle)
{
glBindTexture(GL_TEXTURE_2D, mTexHandle);
glEnable(GL_TEXTURE_2D);
}
// Enable blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Draw the icon as a textured quad
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex2i(mPosX, mPosY);
glTexCoord2f(1.0f, 0.0f);
glVertex2i(mPosX + mWidth, mPosY);
glTexCoord2f(1.0f, 1.0f);
glVertex2i(mPosX + mWidth, mPosY + mHeight);
glTexCoord2f(0.0f, 1.0f);
glVertex2i(mPosX, mPosY + mHeight);
glEnd();
// We're done
glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
}
/// Mouse move function. The function returns true if the state of the
/// button has changed.
bool MouseMove(int x, int y)
{
bool hit = (x >= mPosX) && (x < (mPosX + mWidth)) &&
(y >= mPosY) && (y < (mPosY + mHeight));
bool changed = (mHighlight != hit);
mHighlight = hit;
return changed;
}
/// Mouse click function.
bool MouseClick(int aState, int x, int y)
{
bool hit = (x >= mPosX) && (x < (mPosX + mWidth)) &&
(y >= mPosY) && (y < (mPosY + mHeight));
if(hit && (aState == GLUT_DOWN))
DoAction();
return hit;
}
/// The action function that will be performed when a button is clicked.
virtual void DoAction() {}
GLint mPosX, mPosY;
GLint mWidth, mHeight;
GLViewer * mParent;
};
//-----------------------------------------------------------------------------
// Customized button classes (implementing different actions)
//-----------------------------------------------------------------------------
class OpenButton: public GLButton {
public:
void DoAction()
{
if(!mParent)
return;
mParent->ActionOpenFile();
}
};
class SaveButton: public GLButton {
public:
void DoAction()
{
if(!mParent)
return;
mParent->ActionSaveFile();
}
};
class OpenTextureButton: public GLButton {
public:
void DoAction()
{
if(!mParent)
return;
mParent->ActionOpenTexture();
}
};
class HelpButton: public GLButton {
public:
void DoAction()
{
if(!mParent)
return;
mParent->ActionHelp();
}
};
//-----------------------------------------------------------------------------
// GLUT callback function prototypes
//-----------------------------------------------------------------------------
void GLUTWindowRedraw(void);
void GLUTWindowResize(int w, int h);
void GLUTMouseClick(int button, int state, int x, int y);
void GLUTMouseMove(int x, int y);
void GLUTKeyDown(unsigned char key, int x, int y);
void GLUTSpecialKeyDown(int key, int x, int y);
//-----------------------------------------------------------------------------
// GLViewer: OpenGL related functions
//-----------------------------------------------------------------------------
/// Set up the camera.
void GLViewer::SetupCamera()
{
if(mMesh)
mMesh->BoundingBox(mAABBMin, mAABBMax);
else
{
mAABBMin = Vector3(-1.0f, -1.0f, -1.0f);
mAABBMax = Vector3(1.0f, 1.0f, 1.0f);
}
mCameraLookAt = (mAABBMax + mAABBMin) * 0.5f;
float delta = (mAABBMax - mAABBMin).Abs();
if(mCameraUp.z > 0.0f)
mCameraPosition = Vector3(mCameraLookAt.x,
mCameraLookAt.y - 0.8f * delta,
mCameraLookAt.z + 0.2f * delta);
else
mCameraPosition = Vector3(mCameraLookAt.x,
mCameraLookAt.y + 0.2f * delta,
mCameraLookAt.z + 0.8f * delta);
}
/// Initialize the GLSL shader (requires OpenGL 2.0 or better).
void GLViewer::InitShader()
{
const GLchar * src[1];
// Load vertex shader
mVertShader = glCreateShader(GL_VERTEX_SHADER);
src[0] = (const GLchar *) phongVertSrc;
glShaderSource(mVertShader, 1, src, NULL);
// Load fragment shader
mFragShader = glCreateShader(GL_FRAGMENT_SHADER);
src[0] = (const GLchar *) phongFragSrc;
glShaderSource(mFragShader, 1, src, NULL);
int status;
// Compile the vertex shader
glCompileShader(mVertShader);
glGetShaderiv(mVertShader, GL_COMPILE_STATUS, &status);
if(!status)
throw runtime_error("Could not compile vertex shader.");
// Compile the fragment shader
glCompileShader(mFragShader);
glGetShaderiv(mFragShader, GL_COMPILE_STATUS, &status);
if(!status)
throw runtime_error("Could not compile fragment shader.");
// Link the shader program
mShaderProgram = glCreateProgram();
glAttachShader(mShaderProgram, mVertShader);
glAttachShader(mShaderProgram, mFragShader);
glLinkProgram(mShaderProgram);
glGetProgramiv(mShaderProgram, GL_LINK_STATUS, &status);
if(!status)
throw runtime_error("Could not link shader program.");
mUseShader = true;
}
/// Initialize the texture.
void GLViewer::InitTexture(const char * aFileName)
{
Image image;
// Load texture from a file
if(aFileName)
{
// Check if file exists, and determine actual file name (relative or absolute)
bool fileExists = false;
string name = string(aFileName);
FILE * inFile = fopen(name.c_str(), "rb");
if(inFile)
fileExists = true;
else if(mFilePath.size() > 0)
{
// Try the same path as the mesh file
name = mFilePath + string(aFileName);
inFile = fopen(name.c_str(), "rb");
if(inFile)
fileExists = true;
}
if(inFile)
fclose(inFile);
if(fileExists)
{
cout << "Loading texture (" << aFileName << ")..." << endl;
try
{
image.LoadFromFile(name.c_str());
}
catch(exception &e)
{
cout << "Error loading texture: " << e.what() << endl;
image.Clear();
}
}
}
// If no texture was loaded
if(image.IsEmpty())
{
cout << "Loading texture (dummy)..." << endl;
// Create a default, synthetic texture
image.SetSize(256, 256, 1);
for(int y = 0; y < image.mHeight; ++ y)
{
for(int x = 0; x < image.mWidth; ++ x)
{
if(((x & 0x000f) == 0) || ((y & 0x000f) == 0))
image.mData[y * image.mWidth + x] = 192;
else
image.mData[y * image.mWidth + x] = 255;
}
}
}
// Upload the texture to OpenGL
if(!image.IsEmpty())
glGenTextures(1, &mTexHandle);
else
mTexHandle = 0;
if(mTexHandle)
{
// Determine the color format
GLuint format;
if(image.mComponents == 3)
format = GL_RGB;
else if(image.mComponents == 4)
format = GL_RGBA;
else
format = GL_LUMINANCE;
glBindTexture(GL_TEXTURE_2D, mTexHandle);
if(GLEW_VERSION_1_4)
{
// Generate mipmaps automatically and use them
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
else
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, image.mComponents, image.mWidth, image.mHeight, 0, format, GL_UNSIGNED_BYTE, (GLvoid *) &image.mData[0]);
}
}
/// Set up the scene lighting.
void GLViewer::SetupLighting()
{
GLfloat pos[4], ambient[4], diffuse[4], specular[4];
// Set scene lighting properties
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
ambient[0] = 0.2;
ambient[1] = 0.2;
ambient[2] = 0.2;
ambient[3] = 1.0;
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
// Set-up head light (GL_LIGHT0)
pos[0] = mCameraPosition.x;
pos[1] = mCameraPosition.y;
pos[2] = mCameraPosition.z;
pos[3] = 1.0f;
glLightfv(GL_LIGHT0, GL_POSITION, pos);
ambient[0] = 0.0f;
ambient[1] = 0.0f;
ambient[2] = 0.0f;
ambient[3] = 1.0f;
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
diffuse[0] = 0.8f;
diffuse[1] = 0.8f;
diffuse[2] = 0.8f;
diffuse[3] = 1.0f;
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
specular[0] = 1.0f;
specular[1] = 1.0f;
specular[2] = 1.0f;
specular[3] = 1.0f;
glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
glEnable(GL_LIGHT0);
}
/// Set up the material.
void GLViewer::SetupMaterial()
{
GLfloat specular[4], emission[4];
// Set up the material
specular[0] = 0.3f;
specular[1] = 0.3f;
specular[2] = 0.3f;
specular[3] = 1.0f;
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
emission[0] = 0.0f;
emission[1] = 0.0f;
emission[2] = 0.0f;
emission[3] = 1.0f;
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emission);
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 40.0f);
// Use color material for the diffuse and ambient components
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
}
/// Draw a mesh
void GLViewer::DrawMesh(Mesh * aMesh)
{
// We always have vertices
glVertexPointer(3, GL_FLOAT, 0, &aMesh->mVertices[0]);
glEnableClientState(GL_VERTEX_ARRAY);
// Do we have normals?
if(aMesh->mNormals.size() == aMesh->mVertices.size())
{
glNormalPointer(GL_FLOAT, 0, &aMesh->mNormals[0]);
glEnableClientState(GL_NORMAL_ARRAY);
}
// Do we have texture coordinates?
if(aMesh->mTexCoords.size() == aMesh->mVertices.size())
{
glTexCoordPointer(2, GL_FLOAT, 0, &aMesh->mTexCoords[0]);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
// Do we have colors?
if(aMesh->mColors.size() == aMesh->mVertices.size())
{
glColorPointer(4, GL_FLOAT, 0, &aMesh->mColors[0]);
glEnableClientState(GL_COLOR_ARRAY);
}
// Use glDrawElements to draw the triangles...
glShadeModel(GL_SMOOTH);
if(GLEW_VERSION_1_2)
glDrawRangeElements(GL_TRIANGLES, 0, aMesh->mVertices.size() - 1,
aMesh->mIndices.size(), GL_UNSIGNED_INT,
&aMesh->mIndices[0]);
else
glDrawElements(GL_TRIANGLES, aMesh->mIndices.size(), GL_UNSIGNED_INT,
&aMesh->mIndices[0]);
// We do not use the client state anymore...
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
// Load a file to the mesh
void GLViewer::LoadFile(const char * aFileName, const char * aOverrideTexture)
{
// Get the file size
ifstream f(aFileName, ios::in | ios::binary);
if(f.fail())
throw runtime_error("Unable to open the file.");
f.seekg(0, ios_base::end);
long tmpFileSize = (long) f.tellg();
f.close();
// Load the mesh
cout << "Loading " << aFileName << "..." << flush;
mTimer.Push();
Mesh * newMesh = new Mesh();
try
{
ImportMesh(aFileName, newMesh);
}
catch(exception &e)
{
delete newMesh;
throw;
}
if(mMesh)
delete mMesh;
mMesh = newMesh;
cout << "done (" << int(mTimer.PopDelta() * 1000.0 + 0.5) << " ms)" << endl;
// Get the file name (excluding the path), and the path (excluding the file name)
mFileName = ExtractFileName(string(aFileName));
mFilePath = ExtractFilePath(string(aFileName));
// The temporary file size is now the official file size...
mFileSize = tmpFileSize;
// Set window title
string windowCaption = string("OpenCTM viewer - ") + mFileName;
glutSetWindowTitle(windowCaption.c_str());
// If the file did not contain any normals, calculate them now...
if(mMesh->mNormals.size() != mMesh->mVertices.size())
{
cout << "Calculating normals..." << flush;
mTimer.Push();
mMesh->CalculateNormals();
cout << "done (" << int(mTimer.PopDelta() * 1000.0 + 0.5) << " ms)" << endl;
}
// Load the texture
if(mTexHandle)
glDeleteTextures(1, &mTexHandle);
mTexHandle = 0;
if(mMesh->mTexCoords.size() == mMesh->mVertices.size())
{
string texFileName = mMesh->mTexFileName;
if(aOverrideTexture)
texFileName = string(aOverrideTexture);
if(texFileName.size() > 0)
InitTexture(texFileName.c_str());
else
InitTexture(0);
}
// Setup texture parameters for the shader
if(mUseShader)
{
glUseProgram(mShaderProgram);
// Set the uUseTexture uniform
GLint useTexLoc = glGetUniformLocation(mShaderProgram, "uUseTexture");
if(useTexLoc >= 0)
glUniform1i(useTexLoc, glIsTexture(mTexHandle));
// Set the uTex uniform
GLint texLoc = glGetUniformLocation(mShaderProgram, "uTex");
if(texLoc >= 0)
glUniform1i(texLoc, 0);
glUseProgram(0);
}
// Load the mesh into a displaylist
if(mDisplayList)
glDeleteLists(mDisplayList, 1);
mDisplayList = glGenLists(1);
glNewList(mDisplayList, GL_COMPILE);
DrawMesh(mMesh);
glEndList();
// Init the camera for the new mesh
mCameraUp = Vector3(0.0f, 0.0f, 1.0f);
SetupCamera();
}
// Load a texture file
void GLViewer::LoadTexture(const char * aFileName)
{
// Load the texture
if(mTexHandle)
glDeleteTextures(1, &mTexHandle);
mTexHandle = 0;
if(mMesh->mTexCoords.size() == mMesh->mVertices.size())
InitTexture(aFileName);
// Setup texture parameters for the shader
if(mUseShader)
{
glUseProgram(mShaderProgram);
// Set the uUseTexture uniform
GLint useTexLoc = glGetUniformLocation(mShaderProgram, "uUseTexture");
if(useTexLoc >= 0)
glUniform1i(useTexLoc, glIsTexture(mTexHandle));
// Set the uTex uniform
GLint texLoc = glGetUniformLocation(mShaderProgram, "uTex");
if(texLoc >= 0)
glUniform1i(texLoc, 0);
glUseProgram(0);
}
}
// Draw an outline box.
void GLViewer::DrawOutlineBox(int x1, int y1, int x2, int y2,
float r, float g, float b, float a)
{
// Draw a blended box
// Note: We add (1,1) to the (x2,y2) corner to cover the entire pixel range
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBegin(GL_QUADS);
glColor4f(r, g, b, 0.7f * a);
glVertex2i(x1, y1);
glVertex2i(x2+1, y1);
glColor4f(r, g, b, 0.7f * a + 0.3f);
glVertex2i(x2+1, y2+1);
glVertex2i(x1, y2+1);
glEnd();
glDisable(GL_BLEND);
// Draw a solid outline
glPushMatrix();
glTranslatef(0.5f, 0.5f, 0.0f); // Compensate for 0.5 pixel center offset
glColor4f(r, g, b, 1.0f);
glBegin(GL_LINE_LOOP);
glVertex2i(x1, y1-1);
glVertex2i(x2, y1-1);
glVertex2i(x2+1, y1);
glVertex2i(x2+1, y2);
glVertex2i(x2, y2+1);
glVertex2i(x1, y2+1);
glVertex2i(x1-1, y2);
glVertex2i(x1-1, y1);
glEnd();
glPopMatrix();
}
// Draw a string using GLUT. The string is shown on top of an alpha-blended
// quad.
void GLViewer::DrawString(string aString, int x, int y)
{
// Calculate the size of the string box
int x0 = x, y0 = y;
int x1 = x0, y1 = y0;
int x2 = x0, y2 = y0;
for(unsigned int i = 0; i < aString.size(); ++ i)
{
int c = (int) aString[i];
if(c == (int) 10)
{
x2 = x;
y2 += 13;
}
else if(c != (int) 13)
{
x2 += glutBitmapWidth(GLUT_BITMAP_8_BY_13, c);
if(x2 > x1) x1 = x2;
}
}
y1 = y2 + 13;
// Draw a alpha blended box
DrawOutlineBox(x0-4, y0-3, x1+4, y1+4, 0.3f, 0.3f, 0.3f, 0.6f);
// Print the text
glColor3f(1.0f, 1.0f, 1.0f);
x2 = x;
y2 = y + 13;
for(unsigned int i = 0; i < aString.size(); ++ i)
{
int c = (int) aString[i];
if(c == (int) 10)
{
x2 = x;
y2 += 13;
}
else if(c != (int) 13)
{
glRasterPos2i(x2, y2);
glutBitmapCharacter(GLUT_BITMAP_8_BY_13, c);
x2 += glutBitmapWidth(GLUT_BITMAP_8_BY_13, c);
}
}
}
// Draw 2D overlay
void GLViewer::Draw2DOverlay()
{
// Setup the matrices for a width x height 2D screen
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, (double) mWidth, (double) mHeight, 0.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Setup the rendering pipeline for 2D rendering
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
// Render an info string
if(mMesh)
{
stringstream s;
s << mFileName << " (" << (mFileSize + 512) / 1024 << "KB)" << endl;
s << mMesh->mVertices.size() << " vertices" << endl;
s << mMesh->mIndices.size() / 3 << " triangles";
DrawString(s.str(), 10, mHeight - 50);
}
// Calculate buttons bounding box, and draw it as an outline box
int x1 = 9999, y1 = 9999, x2 = 0, y2 = 0;
for(list<GLButton *>::iterator b = mButtons.begin(); b != mButtons.end(); ++ b)
{
if((*b)->mPosX < x1) x1 = (*b)->mPosX;
if(((*b)->mPosX + (*b)->mWidth) > x2) x2 = (*b)->mPosX + (*b)->mWidth;
if((*b)->mPosY < y1) y1 = (*b)->mPosY;
if(((*b)->mPosY + (*b)->mHeight) > y2) y2 = (*b)->mPosY + (*b)->mHeight;
}
DrawOutlineBox(x1-5, y1-5, x2+5, y2+5, 0.3f, 0.3f, 0.3f, 0.6f);
// Render all the buttons (last = on top)
for(list<GLButton *>::iterator b = mButtons.begin(); b != mButtons.end(); ++ b)
(*b)->Redraw();
}
/// Get 3D coordinate under the mouse cursor.
bool GLViewer::WinCoordTo3DCoord(int x, int y, Vector3 &aPoint)
{
// Read back the depth value at at (x, y)
GLfloat z = 0.0f;
glReadPixels(x, mHeight - y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (GLvoid *) &z);
if((z > 0.0f) && (z < 1.0f))
{
// Convert the window coordinate to space coordinates
GLdouble objX, objY, objZ;
gluUnProject((GLdouble) x, (GLdouble) (mHeight - y), (GLdouble) z,
mModelviewMatrix, mProjectionMatrix, mViewport,
&objX, &objY, &objZ);
aPoint = Vector3((float) objX, (float) objY, (float) objZ);
return true;
}
else
return false;
}
/// Update the focus position of the camera.
void GLViewer::UpdateFocus()
{
double w = (mTimer.GetTime() - mFocusStartTime) / (mFocusEndTime - mFocusStartTime);
Vector3 dir = Normalize(mCameraPosition - mCameraLookAt);
if(w < 1.0)
{
w = pow(w, 0.2);
mCameraLookAt = mFocusStartPos + (mFocusEndPos - mFocusStartPos) * w;
mCameraPosition = mCameraLookAt + dir * (mFocusStartDistance + (mFocusEndDistance - mFocusStartDistance) * w);
}
else
{
mCameraLookAt = mFocusEndPos;
mCameraPosition = mCameraLookAt + dir * mFocusEndDistance;
mFocusing = false;
}
glutPostRedisplay();
}
//-----------------------------------------------------------------------------
// Actions (user activated functions)
//-----------------------------------------------------------------------------
/// Open another file
void GLViewer::ActionOpenFile()
{
SysOpenDialog od;
od.mFilters.push_back(string("All supported 3D files|*.ctm;*.ply;*.stl;*.3ds;*.dae;*.obj;*.lwo;*.off"));
od.mFilters.push_back(string("OpenCTM (.ctm)|*.ctm"));
od.mFilters.push_back(string("Stanford triangle format (.ply)|*.ply"));
od.mFilters.push_back(string("Stereolitography (.stl)|*.stl"));
od.mFilters.push_back(string("3D Studio (.3ds)|*.3ds"));
od.mFilters.push_back(string("COLLADA (.dae)|*.dae"));
od.mFilters.push_back(string("Wavefront geometry file (.obj)|*.obj"));
od.mFilters.push_back(string("LightWave object (.lwo)|*.lwo"));
od.mFilters.push_back(string("Geomview object file format (.off)|*.off"));
if(od.Show())
{
try
{
LoadFile(od.mFileName.c_str(), NULL);
glutPostRedisplay();
}
catch(exception &e)
{
SysMessageBox mb;
mb.mMessageType = SysMessageBox::mtError;
mb.mCaption = "Error";
mb.mText = string(e.what());
mb.Show();
}
}
}
/// Save the file
void GLViewer::ActionSaveFile()
{
if(!mMesh)
{
SysMessageBox mb;
mb.mMessageType = SysMessageBox::mtError;
mb.mCaption = "Save File";
mb.mText = string("No mesh has been loaded.");
mb.Show();
return;
}
SysSaveDialog sd;
sd.mFilters.push_back(string("All files|*"));
sd.mFilters.push_back(string("OpenCTM (.ctm)|*.ctm"));
sd.mFilters.push_back(string("Stanford triangle format (.ply)|*.ply"));
sd.mFilters.push_back(string("Stereolitography (.stl)|*.stl"));
sd.mFilters.push_back(string("3D Studio (.3ds)|*.3ds"));
sd.mFilters.push_back(string("COLLADA (.dae)|*.dae"));
sd.mFilters.push_back(string("Wavefront geometry file (.obj)|*.obj"));
sd.mFilters.push_back(string("LightWave object (.lwo)|*.lwo"));
sd.mFilters.push_back(string("Geomview object file format (.off)|*.off"));
sd.mFilters.push_back(string("VRML 2.0 (.wrl)|*.wrl"));
sd.mFileName = mFileName;
if(sd.Show())
{
try
{
Options opt;
// Do not export normals that do not come from the original file
if(!mMesh->mOriginalNormals)
opt.mNoNormals = true;
// Export the mesh
ExportMesh(sd.mFileName.c_str(), mMesh, opt);
}
catch(exception &e)
{
SysMessageBox mb;
mb.mMessageType = SysMessageBox::mtError;
mb.mCaption = "Error";
mb.mText = string(e.what());
mb.Show();
}
}
}
/// Open a texture file
void GLViewer::ActionOpenTexture()
{
if(!mMesh || (mMesh->mTexCoords.size() < 1))
{
SysMessageBox mb;
mb.mMessageType = SysMessageBox::mtError;
mb.mCaption = "Open Texture File";
mb.mText = string("This mesh does not have any texture coordinates.");
mb.Show();
return;
}
SysOpenDialog od;
od.mCaption = string("Open Texture File");
od.mFilters.push_back(string("All supported texture files|*.jpg;*.jpeg;*.png"));
od.mFilters.push_back(string("JPEG|*.jpg;*.jpeg"));
od.mFilters.push_back(string("PNG|*.png"));
if(od.Show())
{
try
{
LoadTexture(od.mFileName.c_str());
mMesh->mTexFileName = ExtractFileName(od.mFileName);
glutPostRedisplay();
}
catch(exception &e)
{
SysMessageBox mb;
mb.mMessageType = SysMessageBox::mtError;
mb.mCaption = "Error";
mb.mText = string(e.what());
mb.Show();
}
}
}
/// Toggle wire frame view on/off
void GLViewer::ActionToggleWireframe()
{
if(mPolyMode == GL_LINE)
mPolyMode = GL_FILL;
else
mPolyMode = GL_LINE;
glutPostRedisplay();
}
/// Fit model to the screen (re-focus)
void GLViewer::ActionFitToScreen()
{
double now = mTimer.GetTime();
mFocusStartTime = now;
mFocusEndTime = now + FOCUS_TIME;
mFocusStartPos = mCameraLookAt;
mFocusStartDistance = (mCameraLookAt - mCameraPosition).Abs();
mFocusEndPos = (mAABBMax + mAABBMin) * 0.5f;
mFocusEndDistance = 0.825 * (mAABBMax - mAABBMin).Abs();
mFocusing = true;
UpdateFocus();
glutPostRedisplay();
}
/// Set camera up direction to Y
void GLViewer::ActionCameraUpY()
{
mCameraUp = Vector3(0.0f, 1.0f, 0.0f);
SetupCamera();
glutPostRedisplay();
}
/// Set camera up direction to Z
void GLViewer::ActionCameraUpZ()
{
mCameraUp = Vector3(0.0f, 0.0f, 1.0f);
SetupCamera();
glutPostRedisplay();
}
/// Zoom camera one step in
void GLViewer::ActionZoomIn()
{
double now = mTimer.GetTime();
mFocusStartTime = now;
mFocusEndTime = now + FOCUS_TIME;
mFocusStartPos = mCameraLookAt;
mFocusStartDistance = (mCameraLookAt - mCameraPosition).Abs();
mFocusEndPos = mCameraLookAt;
mFocusEndDistance = (1.0/1.5) * mFocusStartDistance;
mFocusing = true;
UpdateFocus();
glutPostRedisplay();
}
/// Zoom camera one step out
void GLViewer::ActionZoomOut()
{
double now = mTimer.GetTime();
mFocusStartTime = now;
mFocusEndTime = now + FOCUS_TIME;
mFocusStartPos = mCameraLookAt;
mFocusStartDistance = (mCameraLookAt - mCameraPosition).Abs();
mFocusEndPos = mCameraLookAt;
mFocusEndDistance = 1.5 * mFocusStartDistance;
mFocusing = true;
UpdateFocus();
glutPostRedisplay();
}
/// Exit program
void GLViewer::ActionExit()
{
// Note: In freeglut you can do glutLeaveMainLoop(), which is more graceful
exit(0);
}
/// Show a help dialog
void GLViewer::ActionHelp()
{
stringstream helpText;
helpText << "ctmviewer - OpenCTM file viewer" << endl;
helpText << "Copyright (c) 2009-2010 Marcus Geelnard" << endl << endl;
helpText << "Keyboard actions:" << endl;
helpText << " W - Toggle wire frame view on/off" << endl;
helpText << " F - Fit model to the screen" << endl;
helpText << " Y - Set Y as the up axis (change camera view)" << endl;
helpText << " Z - Set Z as the up axis (change camera view)" << endl;
helpText << " +/- - Zoom in/out with the camera" << endl;
helpText << " ESC - Exit program" << endl << endl;
helpText << "Mouse control:" << endl;
helpText << " Left button - Rotate camera" << endl;
helpText << " Middle button or wheel - Zoom camera" << endl;
helpText << " Right button - Pan camera" << endl;
helpText << " Double click - Focus on indicated surface";
SysMessageBox mb;
mb.mMessageType = SysMessageBox::mtInformation;
mb.mCaption = "Help";
mb.mText = helpText.str();
mb.Show();
}
//-----------------------------------------------------------------------------
// GLUT callback functions
//-----------------------------------------------------------------------------
/// Redraw function.
void GLViewer::WindowRedraw(void)
{
// Get buffer properties
glGetIntegerv(GL_DEPTH_BITS, &mDepthBufferResolution);
// Set the viewport to be the entire window
glViewport(0, 0, mWidth, mHeight);
// Clear the buffer(s)
glClear(GL_DEPTH_BUFFER_BIT);
// Draw a gradient background
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glBegin(GL_QUADS);
glColor3f(0.4f, 0.5f, 0.7f);
glVertex3f(-1.0f, -1.0f, 0.5f);
glColor3f(0.3f, 0.4f, 0.7f);
glVertex3f(1.0f, -1.0f, 0.5f);
glColor3f(0.1f, 0.1f, 0.2f);
glVertex3f(1.0f, 1.0f, 0.5f);
glColor3f(0.1f, 0.15f, 0.24f);
glVertex3f(-1.0f, 1.0f, 0.5f);
glEnd();
// Calculate screen ratio (width / height)
float ratio;
if(mHeight == 0)
ratio = 1.0f;
else
ratio = (float) mWidth / (float) mHeight;
// Calculate optimal near and far Z clipping planes
float farZ = (mAABBMax - mAABBMin).Abs() +
(mCameraPosition - mCameraLookAt).Abs();
if(farZ < 1e-20f)
farZ = 1e-20f;
float nearZ;
if(mDepthBufferResolution >= 24)
nearZ = 0.0001f * farZ;
else
nearZ = 0.01f * farZ;
// Set up perspective projection
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0f, ratio, nearZ, farZ);
// Set up the camera modelview matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(mCameraPosition.x, mCameraPosition.y, mCameraPosition.z,
mCameraLookAt.x, mCameraLookAt.y, mCameraLookAt.z,
mCameraUp.x, mCameraUp.y, mCameraUp.z);
// Read back camera matrices
glGetDoublev(GL_MODELVIEW_MATRIX, mModelviewMatrix);
glGetDoublev(GL_PROJECTION_MATRIX, mProjectionMatrix);
glGetIntegerv(GL_VIEWPORT, mViewport);
// Set up the lights
SetupLighting();
// Enable material shader
if(mUseShader)
glUseProgram(mShaderProgram);
else
glEnable(GL_LIGHTING);
// Draw the mesh
SetupMaterial();
glEnable(GL_DEPTH_TEST);
glPolygonMode(GL_FRONT_AND_BACK, mPolyMode);
if(mTexHandle)
{
glBindTexture(GL_TEXTURE_2D, mTexHandle);
glEnable(GL_TEXTURE_2D);
glColor3f(1.0f, 1.0f, 1.0f);
}
else
glColor3f(0.9f, 0.86f, 0.7f);
if(mDisplayList)
glCallList(mDisplayList);
glDisable(GL_TEXTURE_2D);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// Disable material shader
if(mUseShader)
glUseProgram(0);
else
glDisable(GL_LIGHTING);
// Draw 2D overlay (information text etc)
Draw2DOverlay();
// Swap buffers
glutSwapBuffers();
// Focusing?
if(mFocusing)
{
UpdateFocus();
glutPostRedisplay();
}
}
/// Resize function.
void GLViewer::WindowResize(int w, int h)
{
// Store the new window size
mWidth = w;
mHeight = h;
}
/// Mouse click function
void GLViewer::MouseClick(int button, int state, int x, int y)
{
bool clickConsumed = false;
if(button == GLUT_LEFT_BUTTON)
{
// Check if any of the GUI buttons were clicked
for(list<GLButton *>::iterator b = mButtons.begin(); b != mButtons.end(); ++ b)
{
if((*b)->MouseClick(state, x, y))
clickConsumed = true;
}
if(!clickConsumed)
{
if(state == GLUT_DOWN)
{
double now = mTimer.GetTime();
if((now - mLastClickTime) < DOUBLE_CLICK_TIME)
{
// Double click occured
Vector3 mouseCoord3D;
if(WinCoordTo3DCoord(x, y, mouseCoord3D))
{
mFocusStartTime = now;
mFocusEndTime = now + FOCUS_TIME;
mFocusStartPos = mCameraLookAt;
mFocusEndPos = mouseCoord3D;
mFocusStartDistance = (mCameraLookAt - mCameraPosition).Abs();
mFocusEndDistance = mFocusStartDistance;
mFocusing = true;
}
mLastClickTime = -1000.0;
}
else
{
// Single click occured
mMouseRotate = true;
mLastClickTime = now;
}
}
else if(state == GLUT_UP)
mMouseRotate = false;
}
}
else if(button == GLUT_MIDDLE_BUTTON)
{
if(state == GLUT_DOWN)
mMouseZoom = true;
else if(state == GLUT_UP)
mMouseZoom = false;
}
else if(button == GLUT_RIGHT_BUTTON)
{
if(state == GLUT_DOWN)
mMousePan = true;
else if(state == GLUT_UP)
mMousePan = false;
}
else if(button == 3) // Mouse wheel up on some systems
{
if(state == GLUT_DOWN)
ActionZoomIn();
}
else if(button == 4) // Mouse wheel down on some systems
{
if(state == GLUT_DOWN)
ActionZoomOut();
}
mOldMouseX = x;
mOldMouseY = y;
// Focusing?
if(mFocusing)
{
UpdateFocus();
glutPostRedisplay();
}
}
/// Mouse move function
void GLViewer::MouseMove(int x, int y)
{
bool needsRedraw = false;
float deltaX = (float) x - (float) mOldMouseX;
float deltaY = (float) y - (float) mOldMouseY;
mOldMouseX = x;
mOldMouseY = y;
if(mMouseRotate)
{
// Calculate delta angles
float scale = 3.0f;
if(mHeight > 0)
scale /= (float) mHeight;
float deltaTheta = -scale * deltaX;
float deltaPhi = -scale * deltaY;
// Adjust camera angles
Vector3 viewVector = mCameraPosition - mCameraLookAt;
float r = sqrtf(viewVector.x * viewVector.x +
viewVector.y * viewVector.y +
viewVector.z * viewVector.z);
float phi, theta;
if(r > 1e-20f)
{
if(mCameraUp.z > 0.0f)
{
phi = acosf(viewVector.z / r);
theta = atan2f(viewVector.y, viewVector.x);
}
else
{
phi = acosf(viewVector.y / r);
theta = atan2f(-viewVector.z, viewVector.x);
}
}
else
{
if(mCameraUp.z > 0.0f)
phi = viewVector.z > 0.0f ? 0.05f * PI : 0.95f * PI;
else
phi = viewVector.y > 0.0f ? 0.05f * PI : 0.95f * PI;
theta = 0.0f;
}
phi += deltaPhi;
theta += deltaTheta;
if(phi > (0.95f * PI))
phi = 0.95f * PI;
else if(phi < (0.05f * PI))
phi = 0.05f * PI;
// Update the camera position
if(mCameraUp.z > 0.0f)
{
viewVector.x = r * cos(theta) * sin(phi);
viewVector.y = r * sin(theta) * sin(phi);
viewVector.z = r * cos(phi);
}
else
{
viewVector.x = r * cos(theta) * sin(phi);
viewVector.y = r * cos(phi);
viewVector.z = -r * sin(theta) * sin(phi);
}
mCameraPosition = mCameraLookAt + viewVector;
needsRedraw = true;
}
else if(mMouseZoom)
{
// Calculate delta angles
float scale = 3.0f;
if(mHeight > 0)
scale /= (float) mHeight;
float zoom = scale * deltaY;
// Adjust camera zoom
Vector3 viewVector = mCameraPosition - mCameraLookAt;
viewVector = viewVector * powf(2.0f, zoom);
// Update the camera position
mCameraPosition = mCameraLookAt + viewVector;
needsRedraw = true;
}
else if(mMousePan)
{
// Calculate delta movement
float scale = 1.0f * (mCameraPosition - mCameraLookAt).Abs();
if(mHeight > 0)
scale /= (float) mHeight;
float panX = scale * deltaX;
float panY = scale * deltaY;
// Calculate camera movement
Vector3 viewDir = Normalize(mCameraPosition - mCameraLookAt);
Vector3 rightDir = Normalize(Cross(viewDir, mCameraUp));
Vector3 upDir = Normalize(Cross(rightDir, viewDir));
Vector3 moveDelta = rightDir * panX + upDir * panY;
// Update the camera position
mCameraPosition += moveDelta;
mCameraLookAt += moveDelta;
needsRedraw = true;
}
else
{
// Call mouse move for all the GUI buttons
for(list<GLButton *>::iterator b = mButtons.begin(); b != mButtons.end(); ++ b)
{
if((*b)->MouseMove(x, y))
needsRedraw = true;
}
}
// Redraw?
if(needsRedraw)
glutPostRedisplay();
}
/// Keyboard function
void GLViewer::KeyDown(unsigned char key, int x, int y)
{
if(key == 15) // CTRL+O
ActionOpenFile();
else if(key == 19) // CTRL+S
ActionSaveFile();
else if(key == 'w')
ActionToggleWireframe();
else if(key == 'f')
ActionFitToScreen();
else if(key == 'y')
ActionCameraUpY();
else if(key == 'z')
ActionCameraUpZ();
else if(key == '+')
ActionZoomIn();
else if(key == '-')
ActionZoomOut();
else if(key == 27) // ESC
ActionExit();
}
/// Keyboard function (special keys)
void GLViewer::SpecialKeyDown(int key, int x, int y)
{
if(key == GLUT_KEY_F1)
ActionHelp();
}
//-----------------------------------------------------------------------------
// Application main code
//-----------------------------------------------------------------------------
/// Constructor
GLViewer::GLViewer()
{
// Clear internal state
mFileName = "";
mFilePath = "";
mFileSize = 0;
mWidth = 1;
mHeight = 1;
mDepthBufferResolution = 16;
mOldMouseX = 0;
mOldMouseY = 0;
mMouseRotate = false;
mMouseZoom = false;
mMousePan = false;
mCameraUp = Vector3(0.0f, 0.0f, 1.0f);
mFocusStartPos = Vector3(0.0f, 0.0f, 0.0f);
mFocusEndPos = Vector3(0.0f, 0.0f, 0.0f);
mFocusStartTime = 0.0;
mFocusEndTime = 0.0;
mFocusStartDistance = 1.0;
mFocusEndDistance = 1.0;
mFocusing = false;
mLastClickTime = -1000.0;
mDisplayList = 0;
mPolyMode = GL_FILL;
mTexHandle = 0;
mUseShader = false;
mShaderProgram = 0;
mVertShader = 0;
mFragShader = 0;
mMesh = NULL;
}
/// Destructor
GLViewer::~GLViewer()
{
// Free all GUI buttons
for(list<GLButton *>::iterator b = mButtons.begin(); b != mButtons.end(); ++ b)
delete (*b);
// Free the mesh
if(mMesh)
delete mMesh;
}
/// Run the application
void GLViewer::Run(int argc, char **argv)
{
try
{
// Init GLUT
glutInit(&argc, argv);
// Create the glut window
glutInitWindowSize(640, 480);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
glutCreateWindow("OpenCTM viewer");
// Init GLEW (for OpenGL 2.x support)
if(glewInit() != GLEW_OK)
throw runtime_error("Unable to initialize GLEW.");
// Load the phong shader, if we can
if(GLEW_VERSION_2_0)
InitShader();
else if(GLEW_VERSION_1_2)
glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
// Set the GLUT callback functions (these are bridged to the corresponding
// class methods)
glutReshapeFunc(GLUTWindowResize);
glutDisplayFunc(GLUTWindowRedraw);
glutMouseFunc(GLUTMouseClick);
glutMotionFunc(GLUTMouseMove);
glutPassiveMotionFunc(GLUTMouseMove);
glutKeyboardFunc(GLUTKeyDown);
glutSpecialFunc(GLUTSpecialKeyDown);
// Create GUI buttons
GLButton * b1 = new OpenButton();
mButtons.push_back(b1);
b1->mParent = this;
b1->SetGlyph(icon_open, 32, 32, 4);
b1->mPosX = 12;
b1->mPosY = 10;
GLButton * b2 = new SaveButton();
mButtons.push_back(b2);
b2->mParent = this;
b2->SetGlyph(icon_save, 32, 32, 4);
b2->mPosX = 60;
b2->mPosY = 10;
GLButton * b3 = new OpenTextureButton();
mButtons.push_back(b3);
b3->mParent = this;
b3->SetGlyph(icon_texture, 32, 32, 4);
b3->mPosX = 108;
b3->mPosY = 10;
GLButton * b4 = new HelpButton();
mButtons.push_back(b4);
b4->mParent = this;
b4->SetGlyph(icon_help, 32, 32, 4);
b4->mPosX = 156;
b4->mPosY = 10;
// Load the file
if(argc >= 2)
{
const char * overrideTexName = NULL;
if(argc >= 3)
overrideTexName = argv[2];
LoadFile(argv[1], overrideTexName);
}
// Enter the main loop
glutMainLoop();
}
catch(ctm_error &e)
{
SysMessageBox mb;
mb.mMessageType = SysMessageBox::mtError;
mb.mCaption = "Error";
mb.mText = string("OpenCTM error: ") + string(e.what());
mb.Show();
}
catch(exception &e)
{
SysMessageBox mb;
mb.mMessageType = SysMessageBox::mtError;
mb.mCaption = "Error";
mb.mText = string(e.what());
mb.Show();
}
cout << endl;
}
//-----------------------------------------------------------------------------
// Bridge GLUT callback functions to class methods
//-----------------------------------------------------------------------------
// NOTE: This is just a hack to be able to reference the application class
// object from the GLUT callback functions, since there is no way (afaik) to
// pass user data (i.e. the object reference) through GLUT...
static GLViewer * gGLViewer = NULL;
/// Redraw function.
void GLUTWindowRedraw(void)
{
if(gGLViewer)
gGLViewer->WindowRedraw();
}
/// Resize function.
void GLUTWindowResize(int w, int h)
{
if(gGLViewer)
gGLViewer->WindowResize(w, h);
}
/// Mouse click function
void GLUTMouseClick(int button, int state, int x, int y)
{
if(gGLViewer)
gGLViewer->MouseClick(button, state, x, y);
}
/// Mouse move function
void GLUTMouseMove(int x, int y)
{
if(gGLViewer)
gGLViewer->MouseMove(x, y);
}
/// Keyboard function
void GLUTKeyDown(unsigned char key, int x, int y)
{
if(gGLViewer)
gGLViewer->KeyDown(key, x, y);
}
/// Keyboard function (special keys)
void GLUTSpecialKeyDown(int key, int x, int y)
{
if(gGLViewer)
gGLViewer->SpecialKeyDown(key, x, y);
}
//-----------------------------------------------------------------------------
// Program startup
//-----------------------------------------------------------------------------
/// Program entry.
int main(int argc, char **argv)
{
// Run the application class
gGLViewer = new GLViewer;
gGLViewer->Run(argc, argv);
delete gGLViewer;
gGLViewer = NULL;
return 0;
}