mirror of
https://github.com/scratchfoundation/bgfx.git
synced 2024-11-29 02:55:36 -05:00
1787 lines
48 KiB
C++
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;
|
|
}
|