1 //-----------------------------------------------------------------------------
2 // Product:     OpenCTM tools
3 // File:        ctmviewer.cpp
4 // Description: 3D file viewer. The program can be used to view OpenCTM files
5 //              in an interactive OpenGL window. Files in other supported
6 //              formats can also be viewed.
7 //-----------------------------------------------------------------------------
8 // Copyright (c) 2009-2010 Marcus Geelnard
9 //
10 // This software is provided 'as-is', without any express or implied
11 // warranty. In no event will the authors be held liable for any damages
12 // arising from the use of this software.
13 //
14 // Permission is granted to anyone to use this software for any purpose,
15 // including commercial applications, and to alter it and redistribute it
16 // freely, subject to the following restrictions:
17 //
18 //     1. The origin of this software must not be misrepresented; you must not
19 //     claim that you wrote the original software. If you use this software
20 //     in a product, an acknowledgment in the product documentation would be
21 //     appreciated but is not required.
22 //
23 //     2. Altered source versions must be plainly marked as such, and must not
24 //     be misrepresented as being the original software.
25 //
26 //     3. This notice may not be removed or altered from any source
27 //     distribution.
28 //-----------------------------------------------------------------------------
29 
30 #include <stdexcept>
31 #include <iostream>
32 #include <fstream>
33 #include <sstream>
34 #include <cstdlib>
35 #include <GL/glew.h>
36 #ifdef __APPLE_CC__
37   #include <GLUT/glut.h>
38 #else
39   #include <GL/glut.h>
40 #endif
41 #include <openctm.h>
42 #include "mesh.h"
43 #include "meshio.h"
44 #include "sysdialog.h"
45 #include "systimer.h"
46 #include "image.h"
47 #include "common.h"
48 
49 using namespace std;
50 
51 
52 // We need PI
53 #ifndef PI
54   #define PI 3.141592653589793238462643f
55 #endif
56 
57 // Configuration constants
58 #define FOCUS_TIME        0.1
59 #define DOUBLE_CLICK_TIME 0.25
60 
61 
62 //-----------------------------------------------------------------------------
63 // GLSL source code (generated from source by bin2c)
64 //-----------------------------------------------------------------------------
65 
66 #include "phong_vert.h"
67 #include "phong_frag.h"
68 
69 
70 //-----------------------------------------------------------------------------
71 // Icon bitmaps
72 //-----------------------------------------------------------------------------
73 
74 #include "icons/icon_open.h"
75 #include "icons/icon_save.h"
76 #include "icons/icon_texture.h"
77 #include "icons/icon_help.h"
78 
79 
80 //-----------------------------------------------------------------------------
81 // The GLViewer application class (declaration)
82 //-----------------------------------------------------------------------------
83 
84 class GLButton;
85 
86 class GLViewer {
87   private:
88     // File information for the current mesh
89     string mFileName, mFilePath;
90     long mFileSize;
91 
92     // Window state cariables
93     int mWidth, mHeight;
94     GLint mDepthBufferResolution;
95     int mOldMouseX, mOldMouseY;
96     double mLastClickTime;
97     bool mMouseRotate;
98     bool mMouseZoom;
99     bool mMousePan;
100     bool mFocusing;
101     Vector3 mFocusStartPos;
102     Vector3 mFocusEndPos;
103     double mFocusStartTime;
104     double mFocusEndTime;
105     double mFocusStartDistance;
106     double mFocusEndDistance;
107 
108     // Camera state
109     Vector3 mCameraPosition;
110     Vector3 mCameraLookAt;
111     Vector3 mCameraUp;
112     GLdouble mModelviewMatrix[16];
113     GLdouble mProjectionMatrix[16];
114     GLint mViewport[4];
115 
116     // Mesh information
117     Mesh * mMesh;
118     Vector3 mAABBMin, mAABBMax;
119     GLuint mDisplayList;
120     GLuint mTexHandle;
121 
122     // Polygon rendering mode (fill / line)
123     GLenum mPolyMode;
124 
125     // GLSL objects
126     bool mUseShader;
127     GLuint mShaderProgram;
128     GLuint mVertShader;
129     GLuint mFragShader;
130 
131     // List of GUI buttons
132     list<GLButton *> mButtons;
133 
134     // Master timer resource
135     SysTimer mTimer;
136 
137     /// Set up the camera.
138     void SetupCamera();
139 
140     /// Initialize the GLSL shader (requires OpenGL 2.0 or better).
141     void InitShader();
142 
143     /// Initialize the texture.
144     void InitTexture(const char * aFileName);
145 
146     /// Set up the scene lighting.
147     void SetupLighting();
148 
149     /// Set up the material.
150     void SetupMaterial();
151 
152     /// Draw a mesh
153     void DrawMesh(Mesh * aMesh);
154 
155     /// Load a file to the mesh
156     void LoadFile(const char * aFileName, const char * aOverrideTexture);
157 
158     /// Load a texture file
159     void LoadTexture(const char * aFileName);
160 
161     /// Draw an outline box.
162     void DrawOutlineBox(int x1, int y1, int x2, int y2,
163       float r, float g, float b, float a);
164 
165     /// Draw a string using GLUT. The string is shown on top of an alpha-blended
166     /// quad.
167     void DrawString(string aString, int x, int y);
168 
169     /// Draw 2D overlay
170     void Draw2DOverlay();
171 
172     /// Get 3D coordinate under the mouse cursor.
173     bool WinCoordTo3DCoord(int x, int y, Vector3 &aPoint);
174 
175     /// Update the focus position of the camera.
176     void UpdateFocus();
177 
178   public:
179     /// Constructor
180     GLViewer();
181 
182     /// Destructor
183     ~GLViewer();
184 
185     /// Open another file
186     void ActionOpenFile();
187 
188     /// Save the file
189     void ActionSaveFile();
190 
191     /// Open a texture file
192     void ActionOpenTexture();
193 
194     /// Toggle wire frame view on/off
195     void ActionToggleWireframe();
196 
197     /// Fit model to the screen (re-focus)
198     void ActionFitToScreen();
199 
200     /// Set camera up direction to Y
201     void ActionCameraUpY();
202 
203     /// Set camera up direction to Z
204     void ActionCameraUpZ();
205 
206     /// Zoom camera one step in
207     void ActionZoomIn();
208 
209     /// Zoom camera one step out
210     void ActionZoomOut();
211 
212     /// Exit program
213     void ActionExit();
214 
215     /// Show a help dialog
216     void ActionHelp();
217 
218     /// Redraw function.
219     void WindowRedraw(void);
220 
221     /// Resize function.
222     void WindowResize(int w, int h);
223 
224     /// Mouse click function
225     void MouseClick(int button, int state, int x, int y);
226 
227     /// Mouse move function
228     void MouseMove(int x, int y);
229 
230     /// Keyboard function
231     void KeyDown(unsigned char key, int x, int y);
232 
233     /// Keyboard function (special keys)
234     void SpecialKeyDown(int key, int x, int y);
235 
236     /// Run the application
237     void Run(int argc, char **argv);
238 };
239 
240 
241 
242 //-----------------------------------------------------------------------------
243 // A class for OpenGL rendered GUI buttons
244 //-----------------------------------------------------------------------------
245 
246 class GLButton {
247   private:
248     // Texture handle
249     GLuint mTexHandle;
250 
251     // Highlight on/off
252     bool mHighlight;
253 
254   public:
255     /// Constructor.
GLButton()256     GLButton()
257     {
258       mTexHandle = 0;
259       mPosX = 0;
260       mPosY = 0;
261       mWidth = 32;
262       mHeight = 32;
263       mHighlight = false;
264       mParent = NULL;
265     }
266 
267     /// Destructor.
~GLButton()268     virtual ~GLButton()
269     {
270       if(mTexHandle)
271         glDeleteTextures(1, &mTexHandle);
272     }
273 
274     /// Set glyph for this button.
SetGlyph(const unsigned char * aBitmap,int aWidth,int aHeight,int aComponents)275     void SetGlyph(const unsigned char * aBitmap, int aWidth, int aHeight,
276       int aComponents)
277     {
278       // Update the button size
279       mWidth = aWidth;
280       mHeight = aHeight;
281 
282       // Upload the texture to OpenGL
283       if(mTexHandle)
284         glDeleteTextures(1, &mTexHandle);
285       glGenTextures(1, &mTexHandle);
286       if(mTexHandle)
287       {
288         // Determine the color format
289         GLuint format;
290         if(aComponents == 3)
291           format = GL_RGB;
292         else if(aComponents == 4)
293           format = GL_RGBA;
294         else
295           format = GL_LUMINANCE;
296 
297         glBindTexture(GL_TEXTURE_2D, mTexHandle);
298 
299         if(GLEW_VERSION_1_4)
300         {
301           // Generate mipmaps automatically and use them
302           glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
303           glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
304         }
305         else
306         {
307           glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
308         }
309         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
310         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
311         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
312 
313         glTexImage2D(GL_TEXTURE_2D, 0, aComponents, aWidth, aHeight, 0,
314                      format, GL_UNSIGNED_BYTE, (GLvoid *) aBitmap);
315       }
316     }
317 
318     /// Redraw function.
Redraw()319     void Redraw()
320     {
321       // Set opacity of the icon
322       if(mHighlight)
323         glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
324       else
325         glColor4f(1.0f, 1.0f, 1.0f, 0.7f);
326 
327       // Enable texturing
328       if(mTexHandle)
329       {
330         glBindTexture(GL_TEXTURE_2D, mTexHandle);
331         glEnable(GL_TEXTURE_2D);
332       }
333 
334       // Enable blending
335       glEnable(GL_BLEND);
336       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
337 
338       // Draw the icon as a textured quad
339       glBegin(GL_QUADS);
340       glTexCoord2f(0.0f, 0.0f);
341       glVertex2i(mPosX, mPosY);
342       glTexCoord2f(1.0f, 0.0f);
343       glVertex2i(mPosX + mWidth, mPosY);
344       glTexCoord2f(1.0f, 1.0f);
345       glVertex2i(mPosX + mWidth, mPosY + mHeight);
346       glTexCoord2f(0.0f, 1.0f);
347       glVertex2i(mPosX, mPosY + mHeight);
348       glEnd();
349 
350       // We're done
351       glDisable(GL_BLEND);
352       glDisable(GL_TEXTURE_2D);
353     }
354 
355     /// Mouse move function. The function returns true if the state of the
356     /// button has changed.
MouseMove(int x,int y)357     bool MouseMove(int x, int y)
358     {
359       bool hit = (x >= mPosX) && (x < (mPosX + mWidth)) &&
360                  (y >= mPosY) && (y < (mPosY + mHeight));
361       bool changed = (mHighlight != hit);
362       mHighlight = hit;
363       return changed;
364     }
365 
366     /// Mouse click function.
MouseClick(int aState,int x,int y)367     bool MouseClick(int aState, int x, int y)
368     {
369       bool hit = (x >= mPosX) && (x < (mPosX + mWidth)) &&
370                  (y >= mPosY) && (y < (mPosY + mHeight));
371       if(hit && (aState == GLUT_DOWN))
372         DoAction();
373       return hit;
374     }
375 
376     /// The action function that will be performed when a button is clicked.
DoAction()377     virtual void DoAction() {}
378 
379     GLint mPosX, mPosY;
380     GLint mWidth, mHeight;
381     GLViewer * mParent;
382 };
383 
384 
385 //-----------------------------------------------------------------------------
386 // Customized button classes (implementing different actions)
387 //-----------------------------------------------------------------------------
388 
389 class OpenButton: public GLButton {
390   public:
DoAction()391     void DoAction()
392     {
393       if(!mParent)
394         return;
395       mParent->ActionOpenFile();
396     }
397 };
398 
399 class SaveButton: public GLButton {
400   public:
DoAction()401     void DoAction()
402     {
403       if(!mParent)
404         return;
405       mParent->ActionSaveFile();
406     }
407 };
408 
409 class OpenTextureButton: public GLButton {
410   public:
DoAction()411     void DoAction()
412     {
413       if(!mParent)
414         return;
415       mParent->ActionOpenTexture();
416     }
417 };
418 
419 class HelpButton: public GLButton {
420   public:
DoAction()421     void DoAction()
422     {
423       if(!mParent)
424         return;
425       mParent->ActionHelp();
426     }
427 };
428 
429 
430 //-----------------------------------------------------------------------------
431 // GLUT callback function prototypes
432 //-----------------------------------------------------------------------------
433 
434 void GLUTWindowRedraw(void);
435 void GLUTWindowResize(int w, int h);
436 void GLUTMouseClick(int button, int state, int x, int y);
437 void GLUTMouseMove(int x, int y);
438 void GLUTKeyDown(unsigned char key, int x, int y);
439 void GLUTSpecialKeyDown(int key, int x, int y);
440 
441 
442 //-----------------------------------------------------------------------------
443 // GLViewer: OpenGL related functions
444 //-----------------------------------------------------------------------------
445 
446 /// Set up the camera.
SetupCamera()447 void GLViewer::SetupCamera()
448 {
449   if(mMesh)
450     mMesh->BoundingBox(mAABBMin, mAABBMax);
451   else
452   {
453     mAABBMin = Vector3(-1.0f, -1.0f, -1.0f);
454     mAABBMax = Vector3(1.0f, 1.0f, 1.0f);
455   }
456   mCameraLookAt = (mAABBMax + mAABBMin) * 0.5f;
457   float delta = (mAABBMax - mAABBMin).Abs();
458   if(mCameraUp.z > 0.0f)
459     mCameraPosition = Vector3(mCameraLookAt.x,
460                               mCameraLookAt.y - 0.8f * delta,
461                               mCameraLookAt.z + 0.2f * delta);
462   else
463     mCameraPosition = Vector3(mCameraLookAt.x,
464                               mCameraLookAt.y + 0.2f * delta,
465                               mCameraLookAt.z + 0.8f * delta);
466 }
467 
468 /// Initialize the GLSL shader (requires OpenGL 2.0 or better).
InitShader()469 void GLViewer::InitShader()
470 {
471   const GLchar * src[1];
472 
473   // Load vertex shader
474   mVertShader = glCreateShader(GL_VERTEX_SHADER);
475   src[0] = (const GLchar *) phongVertSrc;
476   glShaderSource(mVertShader, 1, src, NULL);
477 
478   // Load fragment shader
479   mFragShader = glCreateShader(GL_FRAGMENT_SHADER);
480   src[0] = (const GLchar *) phongFragSrc;
481   glShaderSource(mFragShader, 1, src, NULL);
482 
483   int status;
484 
485   // Compile the vertex shader
486   glCompileShader(mVertShader);
487   glGetShaderiv(mVertShader, GL_COMPILE_STATUS, &status);
488   if(!status)
489     throw runtime_error("Could not compile vertex shader.");
490 
491   // Compile the fragment shader
492   glCompileShader(mFragShader);
493   glGetShaderiv(mFragShader, GL_COMPILE_STATUS, &status);
494   if(!status)
495     throw runtime_error("Could not compile fragment shader.");
496 
497   // Link the shader program
498   mShaderProgram = glCreateProgram();
499   glAttachShader(mShaderProgram, mVertShader);
500   glAttachShader(mShaderProgram, mFragShader);
501   glLinkProgram(mShaderProgram);
502   glGetProgramiv(mShaderProgram, GL_LINK_STATUS, &status);
503   if(!status)
504     throw runtime_error("Could not link shader program.");
505 
506   mUseShader = true;
507 }
508 
509 /// Initialize the texture.
InitTexture(const char * aFileName)510 void GLViewer::InitTexture(const char * aFileName)
511 {
512   Image image;
513 
514   // Load texture from a file
515   if(aFileName)
516   {
517     // Check if file exists, and determine actual file name (relative or absolute)
518     bool fileExists = false;
519     string name = string(aFileName);
520     FILE * inFile = fopen(name.c_str(), "rb");
521     if(inFile)
522       fileExists = true;
523     else if(mFilePath.size() > 0)
524     {
525       // Try the same path as the mesh file
526       name = mFilePath + string(aFileName);
527       inFile = fopen(name.c_str(), "rb");
528       if(inFile)
529         fileExists = true;
530     }
531     if(inFile)
532       fclose(inFile);
533 
534     if(fileExists)
535     {
536       cout << "Loading texture (" << aFileName << ")..." << endl;
537       try
538       {
539         image.LoadFromFile(name.c_str());
540       }
541       catch(exception &e)
542       {
543         cout << "Error loading texture: " << e.what() << endl;
544         image.Clear();
545       }
546     }
547   }
548 
549   // If no texture was loaded
550   if(image.IsEmpty())
551   {
552     cout << "Loading texture (dummy)..." << endl;
553 
554     // Create a default, synthetic texture
555     image.SetSize(256, 256, 1);
556     for(int y = 0; y < image.mHeight; ++ y)
557     {
558       for(int x = 0; x < image.mWidth; ++ x)
559       {
560         if(((x & 0x000f) == 0) || ((y & 0x000f) == 0))
561           image.mData[y * image.mWidth + x] = 192;
562         else
563           image.mData[y * image.mWidth + x] = 255;
564       }
565     }
566   }
567 
568   // Upload the texture to OpenGL
569   if(!image.IsEmpty())
570     glGenTextures(1, &mTexHandle);
571   else
572     mTexHandle = 0;
573   if(mTexHandle)
574   {
575     // Determine the color format
576     GLuint format;
577     if(image.mComponents == 3)
578       format = GL_RGB;
579     else if(image.mComponents == 4)
580       format = GL_RGBA;
581     else
582       format = GL_LUMINANCE;
583 
584     glBindTexture(GL_TEXTURE_2D, mTexHandle);
585 
586     if(GLEW_VERSION_1_4)
587     {
588       // Generate mipmaps automatically and use them
589       glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
590       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
591     }
592     else
593     {
594       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
595     }
596     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
597     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
598     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
599 
600     glTexImage2D(GL_TEXTURE_2D, 0, image.mComponents, image.mWidth, image.mHeight, 0, format, GL_UNSIGNED_BYTE, (GLvoid *) &image.mData[0]);
601   }
602 }
603 
604 /// Set up the scene lighting.
SetupLighting()605 void GLViewer::SetupLighting()
606 {
607   GLfloat pos[4], ambient[4], diffuse[4], specular[4];
608 
609   // Set scene lighting properties
610   glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
611   glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
612   ambient[0] = 0.2;
613   ambient[1] = 0.2;
614   ambient[2] = 0.2;
615   ambient[3] = 1.0;
616   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
617 
618   // Set-up head light (GL_LIGHT0)
619   pos[0] = mCameraPosition.x;
620   pos[1] = mCameraPosition.y;
621   pos[2] = mCameraPosition.z;
622   pos[3] = 1.0f;
623   glLightfv(GL_LIGHT0, GL_POSITION, pos);
624   ambient[0] = 0.0f;
625   ambient[1] = 0.0f;
626   ambient[2] = 0.0f;
627   ambient[3] = 1.0f;
628   glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
629   diffuse[0] = 0.8f;
630   diffuse[1] = 0.8f;
631   diffuse[2] = 0.8f;
632   diffuse[3] = 1.0f;
633   glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
634   specular[0] = 1.0f;
635   specular[1] = 1.0f;
636   specular[2] = 1.0f;
637   specular[3] = 1.0f;
638   glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
639   glEnable(GL_LIGHT0);
640 }
641 
642 /// Set up the material.
SetupMaterial()643 void GLViewer::SetupMaterial()
644 {
645   GLfloat specular[4], emission[4];
646 
647   // Set up the material
648   specular[0] = 0.3f;
649   specular[1] = 0.3f;
650   specular[2] = 0.3f;
651   specular[3] = 1.0f;
652   glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
653   emission[0] = 0.0f;
654   emission[1] = 0.0f;
655   emission[2] = 0.0f;
656   emission[3] = 1.0f;
657   glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emission);
658   glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 40.0f);
659 
660   // Use color material for the diffuse and ambient components
661   glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
662   glEnable(GL_COLOR_MATERIAL);
663 }
664 
665 /// Draw a mesh
DrawMesh(Mesh * aMesh)666 void GLViewer::DrawMesh(Mesh * aMesh)
667 {
668   // We always have vertices
669   glVertexPointer(3, GL_FLOAT, 0, &aMesh->mVertices[0]);
670   glEnableClientState(GL_VERTEX_ARRAY);
671 
672   // Do we have normals?
673   if(aMesh->mNormals.size() == aMesh->mVertices.size())
674   {
675     glNormalPointer(GL_FLOAT, 0, &aMesh->mNormals[0]);
676     glEnableClientState(GL_NORMAL_ARRAY);
677   }
678 
679   // Do we have texture coordinates?
680   if(aMesh->mTexCoords.size() == aMesh->mVertices.size())
681   {
682     glTexCoordPointer(2, GL_FLOAT, 0, &aMesh->mTexCoords[0]);
683     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
684   }
685 
686   // Do we have colors?
687   if(aMesh->mColors.size() == aMesh->mVertices.size())
688   {
689     glColorPointer(4, GL_FLOAT, 0, &aMesh->mColors[0]);
690     glEnableClientState(GL_COLOR_ARRAY);
691   }
692 
693   // Use glDrawElements to draw the triangles...
694   glShadeModel(GL_SMOOTH);
695   if(GLEW_VERSION_1_2)
696     glDrawRangeElements(GL_TRIANGLES, 0, aMesh->mVertices.size() - 1,
697                         aMesh->mIndices.size(), GL_UNSIGNED_INT,
698                         &aMesh->mIndices[0]);
699   else
700     glDrawElements(GL_TRIANGLES, aMesh->mIndices.size(), GL_UNSIGNED_INT,
701                    &aMesh->mIndices[0]);
702 
703   // We do not use the client state anymore...
704   glDisableClientState(GL_VERTEX_ARRAY);
705   glDisableClientState(GL_NORMAL_ARRAY);
706   glDisableClientState(GL_TEXTURE_COORD_ARRAY);
707   glDisableClientState(GL_COLOR_ARRAY);
708 }
709 
710 // Load a file to the mesh
LoadFile(const char * aFileName,const char * aOverrideTexture)711 void GLViewer::LoadFile(const char * aFileName, const char * aOverrideTexture)
712 {
713   // Get the file size
714   ifstream f(aFileName, ios::in | ios::binary);
715   if(f.fail())
716     throw runtime_error("Unable to open the file.");
717   f.seekg(0, ios_base::end);
718   long tmpFileSize = (long) f.tellg();
719   f.close();
720 
721   // Load the mesh
722   cout << "Loading " << aFileName << "..." << flush;
723   mTimer.Push();
724   Mesh * newMesh = new Mesh();
725   try
726   {
727     ImportMesh(aFileName, newMesh);
728   }
729   catch(exception &e)
730   {
731     delete newMesh;
732     throw;
733   }
734   if(mMesh)
735     delete mMesh;
736   mMesh = newMesh;
737   cout << "done (" << int(mTimer.PopDelta() * 1000.0 + 0.5) << " ms)" << endl;
738 
739   // Get the file name (excluding the path), and the path (excluding the file name)
740   mFileName = ExtractFileName(string(aFileName));
741   mFilePath = ExtractFilePath(string(aFileName));
742 
743   // The temporary file size is now the official file size...
744   mFileSize = tmpFileSize;
745 
746   // Set window title
747   string windowCaption = string("OpenCTM viewer - ") + mFileName;
748   glutSetWindowTitle(windowCaption.c_str());
749 
750   // If the file did not contain any normals, calculate them now...
751   if(mMesh->mNormals.size() != mMesh->mVertices.size())
752   {
753     cout << "Calculating normals..." << flush;
754     mTimer.Push();
755     mMesh->CalculateNormals();
756     cout << "done (" << int(mTimer.PopDelta() * 1000.0 + 0.5) << " ms)" << endl;
757   }
758 
759   // Load the texture
760   if(mTexHandle)
761     glDeleteTextures(1, &mTexHandle);
762   mTexHandle = 0;
763   if(mMesh->mTexCoords.size() == mMesh->mVertices.size())
764   {
765     string texFileName = mMesh->mTexFileName;
766     if(aOverrideTexture)
767       texFileName = string(aOverrideTexture);
768     if(texFileName.size() > 0)
769       InitTexture(texFileName.c_str());
770     else
771       InitTexture(0);
772   }
773 
774   // Setup texture parameters for the shader
775   if(mUseShader)
776   {
777     glUseProgram(mShaderProgram);
778 
779     // Set the uUseTexture uniform
780     GLint useTexLoc = glGetUniformLocation(mShaderProgram, "uUseTexture");
781     if(useTexLoc >= 0)
782       glUniform1i(useTexLoc, glIsTexture(mTexHandle));
783 
784     // Set the uTex uniform
785     GLint texLoc = glGetUniformLocation(mShaderProgram, "uTex");
786     if(texLoc >= 0)
787       glUniform1i(texLoc, 0);
788 
789     glUseProgram(0);
790   }
791 
792   // Load the mesh into a displaylist
793   if(mDisplayList)
794     glDeleteLists(mDisplayList, 1);
795   mDisplayList = glGenLists(1);
796   glNewList(mDisplayList, GL_COMPILE);
797   DrawMesh(mMesh);
798   glEndList();
799 
800   // Init the camera for the new mesh
801   mCameraUp = Vector3(0.0f, 0.0f, 1.0f);
802   SetupCamera();
803 }
804 
805 // Load a texture file
LoadTexture(const char * aFileName)806 void GLViewer::LoadTexture(const char * aFileName)
807 {
808   // Load the texture
809   if(mTexHandle)
810     glDeleteTextures(1, &mTexHandle);
811   mTexHandle = 0;
812   if(mMesh->mTexCoords.size() == mMesh->mVertices.size())
813     InitTexture(aFileName);
814 
815   // Setup texture parameters for the shader
816   if(mUseShader)
817   {
818     glUseProgram(mShaderProgram);
819 
820     // Set the uUseTexture uniform
821     GLint useTexLoc = glGetUniformLocation(mShaderProgram, "uUseTexture");
822     if(useTexLoc >= 0)
823       glUniform1i(useTexLoc, glIsTexture(mTexHandle));
824 
825     // Set the uTex uniform
826     GLint texLoc = glGetUniformLocation(mShaderProgram, "uTex");
827     if(texLoc >= 0)
828       glUniform1i(texLoc, 0);
829 
830     glUseProgram(0);
831   }
832 }
833 
834 // Draw an outline box.
DrawOutlineBox(int x1,int y1,int x2,int y2,float r,float g,float b,float a)835 void GLViewer::DrawOutlineBox(int x1, int y1, int x2, int y2,
836   float r, float g, float b, float a)
837 {
838   // Draw a blended box
839   // Note: We add (1,1) to the (x2,y2) corner to cover the entire pixel range
840   glEnable(GL_BLEND);
841   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
842   glBegin(GL_QUADS);
843   glColor4f(r, g, b, 0.7f * a);
844   glVertex2i(x1, y1);
845   glVertex2i(x2+1, y1);
846   glColor4f(r, g, b, 0.7f * a + 0.3f);
847   glVertex2i(x2+1, y2+1);
848   glVertex2i(x1, y2+1);
849   glEnd();
850   glDisable(GL_BLEND);
851 
852   // Draw a solid outline
853   glPushMatrix();
854   glTranslatef(0.5f, 0.5f, 0.0f);  // Compensate for 0.5 pixel center offset
855   glColor4f(r, g, b, 1.0f);
856   glBegin(GL_LINE_LOOP);
857   glVertex2i(x1, y1-1);
858   glVertex2i(x2, y1-1);
859   glVertex2i(x2+1, y1);
860   glVertex2i(x2+1, y2);
861   glVertex2i(x2, y2+1);
862   glVertex2i(x1, y2+1);
863   glVertex2i(x1-1, y2);
864   glVertex2i(x1-1, y1);
865   glEnd();
866   glPopMatrix();
867 }
868 
869 // Draw a string using GLUT. The string is shown on top of an alpha-blended
870 // quad.
DrawString(string aString,int x,int y)871 void GLViewer::DrawString(string aString, int x, int y)
872 {
873   // Calculate the size of the string box
874   int x0 = x, y0 = y;
875   int x1 = x0, y1 = y0;
876   int x2 = x0, y2 = y0;
877   for(unsigned int i = 0; i < aString.size(); ++ i)
878   {
879     int c = (int) aString[i];
880     if(c == (int) 10)
881     {
882       x2 = x;
883       y2 += 13;
884     }
885     else if(c != (int) 13)
886     {
887       x2 += glutBitmapWidth(GLUT_BITMAP_8_BY_13, c);
888       if(x2 > x1) x1 = x2;
889     }
890   }
891   y1 = y2 + 13;
892 
893   // Draw a alpha blended box
894   DrawOutlineBox(x0-4, y0-3, x1+4, y1+4, 0.3f, 0.3f, 0.3f, 0.6f);
895 
896   // Print the text
897   glColor3f(1.0f, 1.0f, 1.0f);
898   x2 = x;
899   y2 = y + 13;
900   for(unsigned int i = 0; i < aString.size(); ++ i)
901   {
902     int c = (int) aString[i];
903     if(c == (int) 10)
904     {
905       x2 = x;
906       y2 += 13;
907     }
908     else if(c != (int) 13)
909     {
910       glRasterPos2i(x2, y2);
911       glutBitmapCharacter(GLUT_BITMAP_8_BY_13, c);
912       x2 += glutBitmapWidth(GLUT_BITMAP_8_BY_13, c);
913     }
914   }
915 }
916 
917 // Draw 2D overlay
Draw2DOverlay()918 void GLViewer::Draw2DOverlay()
919 {
920   // Setup the matrices for a width x height 2D screen
921   glMatrixMode(GL_PROJECTION);
922   glLoadIdentity();
923   glOrtho(0.0, (double) mWidth, (double) mHeight, 0.0, -1.0, 1.0);
924   glMatrixMode(GL_MODELVIEW);
925   glLoadIdentity();
926 
927   // Setup the rendering pipeline for 2D rendering
928   glDisable(GL_LIGHTING);
929   glDisable(GL_DEPTH_TEST);
930 
931   // Render an info string
932   if(mMesh)
933   {
934     stringstream s;
935     s << mFileName << " (" << (mFileSize + 512) / 1024 << "KB)" << endl;
936     s << mMesh->mVertices.size() << " vertices" << endl;
937     s << mMesh->mIndices.size() / 3 << " triangles";
938     DrawString(s.str(), 10, mHeight - 50);
939   }
940 
941   // Calculate buttons bounding box, and draw it as an outline box
942   int x1 = 9999, y1 = 9999, x2 = 0, y2 = 0;
943   for(list<GLButton *>::iterator b = mButtons.begin(); b != mButtons.end(); ++ b)
944   {
945     if((*b)->mPosX < x1) x1 = (*b)->mPosX;
946     if(((*b)->mPosX + (*b)->mWidth) > x2) x2 = (*b)->mPosX + (*b)->mWidth;
947     if((*b)->mPosY < y1) y1 = (*b)->mPosY;
948     if(((*b)->mPosY + (*b)->mHeight) > y2) y2 = (*b)->mPosY + (*b)->mHeight;
949   }
950   DrawOutlineBox(x1-5, y1-5, x2+5, y2+5, 0.3f, 0.3f, 0.3f, 0.6f);
951 
952   // Render all the buttons (last = on top)
953   for(list<GLButton *>::iterator b = mButtons.begin(); b != mButtons.end(); ++ b)
954     (*b)->Redraw();
955 }
956 
957 /// Get 3D coordinate under the mouse cursor.
WinCoordTo3DCoord(int x,int y,Vector3 & aPoint)958 bool GLViewer::WinCoordTo3DCoord(int x, int y, Vector3 &aPoint)
959 {
960   // Read back the depth value at at (x, y)
961   GLfloat z = 0.0f;
962   glReadPixels(x,  mHeight - y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (GLvoid *) &z);
963   if((z > 0.0f) && (z < 1.0f))
964   {
965     // Convert the window coordinate to space coordinates
966     GLdouble objX, objY, objZ;
967     gluUnProject((GLdouble) x, (GLdouble) (mHeight - y), (GLdouble) z,
968                  mModelviewMatrix, mProjectionMatrix, mViewport,
969                  &objX, &objY, &objZ);
970     aPoint = Vector3((float) objX, (float) objY, (float) objZ);
971     return true;
972   }
973   else
974     return false;
975 }
976 
977 /// Update the focus position of the camera.
UpdateFocus()978 void GLViewer::UpdateFocus()
979 {
980   double w = (mTimer.GetTime() - mFocusStartTime) / (mFocusEndTime - mFocusStartTime);
981   Vector3 dir = Normalize(mCameraPosition - mCameraLookAt);
982   if(w < 1.0)
983   {
984     w = pow(w, 0.2);
985     mCameraLookAt = mFocusStartPos + (mFocusEndPos - mFocusStartPos) * w;
986     mCameraPosition = mCameraLookAt + dir * (mFocusStartDistance + (mFocusEndDistance - mFocusStartDistance) * w);
987   }
988   else
989   {
990     mCameraLookAt = mFocusEndPos;
991     mCameraPosition = mCameraLookAt + dir * mFocusEndDistance;
992     mFocusing = false;
993   }
994   glutPostRedisplay();
995 }
996 
997 
998 //-----------------------------------------------------------------------------
999 // Actions (user activated functions)
1000 //-----------------------------------------------------------------------------
1001 
1002 /// Open another file
ActionOpenFile()1003 void GLViewer::ActionOpenFile()
1004 {
1005   SysOpenDialog od;
1006   od.mFilters.push_back(string("All supported 3D files|*.ctm;*.ply;*.stl;*.3ds;*.dae;*.obj;*.lwo;*.off"));
1007   od.mFilters.push_back(string("OpenCTM (.ctm)|*.ctm"));
1008   od.mFilters.push_back(string("Stanford triangle format (.ply)|*.ply"));
1009   od.mFilters.push_back(string("Stereolitography (.stl)|*.stl"));
1010   od.mFilters.push_back(string("3D Studio (.3ds)|*.3ds"));
1011   od.mFilters.push_back(string("COLLADA (.dae)|*.dae"));
1012   od.mFilters.push_back(string("Wavefront geometry file (.obj)|*.obj"));
1013   od.mFilters.push_back(string("LightWave object (.lwo)|*.lwo"));
1014   od.mFilters.push_back(string("Geomview object file format (.off)|*.off"));
1015   if(od.Show())
1016   {
1017     try
1018     {
1019       LoadFile(od.mFileName.c_str(), NULL);
1020       glutPostRedisplay();
1021     }
1022     catch(exception &e)
1023     {
1024       SysMessageBox mb;
1025       mb.mMessageType = SysMessageBox::mtError;
1026       mb.mCaption = "Error";
1027       mb.mText = string(e.what());
1028       mb.Show();
1029     }
1030   }
1031 }
1032 
1033 /// Save the file
ActionSaveFile()1034 void GLViewer::ActionSaveFile()
1035 {
1036   if(!mMesh)
1037   {
1038     SysMessageBox mb;
1039     mb.mMessageType = SysMessageBox::mtError;
1040     mb.mCaption = "Save File";
1041     mb.mText = string("No mesh has been loaded.");
1042     mb.Show();
1043     return;
1044   }
1045 
1046   SysSaveDialog sd;
1047   sd.mFilters.push_back(string("All files|*"));
1048   sd.mFilters.push_back(string("OpenCTM (.ctm)|*.ctm"));
1049   sd.mFilters.push_back(string("Stanford triangle format (.ply)|*.ply"));
1050   sd.mFilters.push_back(string("Stereolitography (.stl)|*.stl"));
1051   sd.mFilters.push_back(string("3D Studio (.3ds)|*.3ds"));
1052   sd.mFilters.push_back(string("COLLADA (.dae)|*.dae"));
1053   sd.mFilters.push_back(string("Wavefront geometry file (.obj)|*.obj"));
1054   sd.mFilters.push_back(string("LightWave object (.lwo)|*.lwo"));
1055   sd.mFilters.push_back(string("Geomview object file format (.off)|*.off"));
1056   sd.mFilters.push_back(string("VRML 2.0 (.wrl)|*.wrl"));
1057   sd.mFileName = mFileName;
1058   if(sd.Show())
1059   {
1060     try
1061     {
1062       Options opt;
1063 
1064       // Do not export normals that do not come from the original file
1065       if(!mMesh->mOriginalNormals)
1066         opt.mNoNormals = true;
1067 
1068       // Export the mesh
1069       ExportMesh(sd.mFileName.c_str(), mMesh, opt);
1070     }
1071     catch(exception &e)
1072     {
1073       SysMessageBox mb;
1074       mb.mMessageType = SysMessageBox::mtError;
1075       mb.mCaption = "Error";
1076       mb.mText = string(e.what());
1077       mb.Show();
1078     }
1079   }
1080 }
1081 
1082 /// Open a texture file
ActionOpenTexture()1083 void GLViewer::ActionOpenTexture()
1084 {
1085   if(!mMesh || (mMesh->mTexCoords.size() < 1))
1086   {
1087     SysMessageBox mb;
1088     mb.mMessageType = SysMessageBox::mtError;
1089     mb.mCaption = "Open Texture File";
1090     mb.mText = string("This mesh does not have any texture coordinates.");
1091     mb.Show();
1092     return;
1093   }
1094 
1095   SysOpenDialog od;
1096   od.mCaption = string("Open Texture File");
1097   od.mFilters.push_back(string("All supported texture files|*.jpg;*.jpeg;*.png"));
1098   od.mFilters.push_back(string("JPEG|*.jpg;*.jpeg"));
1099   od.mFilters.push_back(string("PNG|*.png"));
1100   if(od.Show())
1101   {
1102     try
1103     {
1104       LoadTexture(od.mFileName.c_str());
1105       mMesh->mTexFileName = ExtractFileName(od.mFileName);
1106       glutPostRedisplay();
1107     }
1108     catch(exception &e)
1109     {
1110       SysMessageBox mb;
1111       mb.mMessageType = SysMessageBox::mtError;
1112       mb.mCaption = "Error";
1113       mb.mText = string(e.what());
1114       mb.Show();
1115     }
1116   }
1117 }
1118 
1119 /// Toggle wire frame view on/off
ActionToggleWireframe()1120 void GLViewer::ActionToggleWireframe()
1121 {
1122   if(mPolyMode == GL_LINE)
1123     mPolyMode = GL_FILL;
1124   else
1125     mPolyMode = GL_LINE;
1126   glutPostRedisplay();
1127 }
1128 
1129 /// Fit model to the screen (re-focus)
ActionFitToScreen()1130 void GLViewer::ActionFitToScreen()
1131 {
1132   double now = mTimer.GetTime();
1133   mFocusStartTime = now;
1134   mFocusEndTime = now + FOCUS_TIME;
1135   mFocusStartPos = mCameraLookAt;
1136   mFocusStartDistance = (mCameraLookAt - mCameraPosition).Abs();
1137   mFocusEndPos = (mAABBMax + mAABBMin) * 0.5f;
1138   mFocusEndDistance = 0.825 * (mAABBMax - mAABBMin).Abs();
1139   mFocusing = true;
1140   UpdateFocus();
1141   glutPostRedisplay();
1142 }
1143 
1144 /// Set camera up direction to Y
ActionCameraUpY()1145 void GLViewer::ActionCameraUpY()
1146 {
1147   mCameraUp = Vector3(0.0f, 1.0f, 0.0f);
1148   SetupCamera();
1149   glutPostRedisplay();
1150 }
1151 
1152 /// Set camera up direction to Z
ActionCameraUpZ()1153 void GLViewer::ActionCameraUpZ()
1154 {
1155   mCameraUp = Vector3(0.0f, 0.0f, 1.0f);
1156   SetupCamera();
1157   glutPostRedisplay();
1158 }
1159 
1160 /// Zoom camera one step in
ActionZoomIn()1161 void GLViewer::ActionZoomIn()
1162 {
1163   double now = mTimer.GetTime();
1164   mFocusStartTime = now;
1165   mFocusEndTime = now + FOCUS_TIME;
1166   mFocusStartPos = mCameraLookAt;
1167   mFocusStartDistance = (mCameraLookAt - mCameraPosition).Abs();
1168   mFocusEndPos = mCameraLookAt;
1169   mFocusEndDistance = (1.0/1.5) * mFocusStartDistance;
1170   mFocusing = true;
1171   UpdateFocus();
1172   glutPostRedisplay();
1173 }
1174 
1175 /// Zoom camera one step out
ActionZoomOut()1176 void GLViewer::ActionZoomOut()
1177 {
1178   double now = mTimer.GetTime();
1179   mFocusStartTime = now;
1180   mFocusEndTime = now + FOCUS_TIME;
1181   mFocusStartPos = mCameraLookAt;
1182   mFocusStartDistance = (mCameraLookAt - mCameraPosition).Abs();
1183   mFocusEndPos = mCameraLookAt;
1184   mFocusEndDistance = 1.5 * mFocusStartDistance;
1185   mFocusing = true;
1186   UpdateFocus();
1187   glutPostRedisplay();
1188 }
1189 
1190 /// Exit program
ActionExit()1191 void GLViewer::ActionExit()
1192 {
1193   // Note: In freeglut you can do glutLeaveMainLoop(), which is more graceful
1194   exit(0);
1195 }
1196 
1197 /// Show a help dialog
ActionHelp()1198 void GLViewer::ActionHelp()
1199 {
1200   stringstream helpText;
1201   helpText << "ctmviewer - OpenCTM file viewer" << endl;
1202   helpText << "Copyright (c) 2009-2010 Marcus Geelnard" << endl << endl;
1203   helpText << "Keyboard actions:" << endl;
1204   helpText << "  W - Toggle wire frame view on/off" << endl;
1205   helpText << "  F - Fit model to the screen" << endl;
1206   helpText << "  Y - Set Y as the up axis (change camera view)" << endl;
1207   helpText << "  Z - Set Z as the up axis (change camera view)" << endl;
1208   helpText << "  +/- - Zoom in/out with the camera" << endl;
1209   helpText << "  ESC - Exit program" << endl << endl;
1210   helpText << "Mouse control:" << endl;
1211   helpText << "  Left button - Rotate camera" << endl;
1212   helpText << "  Middle button or wheel - Zoom camera" << endl;
1213   helpText << "  Right button - Pan camera" << endl;
1214   helpText << "  Double click - Focus on indicated surface";
1215 
1216   SysMessageBox mb;
1217   mb.mMessageType = SysMessageBox::mtInformation;
1218   mb.mCaption = "Help";
1219   mb.mText = helpText.str();
1220   mb.Show();
1221 }
1222 
1223 
1224 //-----------------------------------------------------------------------------
1225 // GLUT callback functions
1226 //-----------------------------------------------------------------------------
1227 
1228 /// Redraw function.
WindowRedraw(void)1229 void GLViewer::WindowRedraw(void)
1230 {
1231   // Get buffer properties
1232   glGetIntegerv(GL_DEPTH_BITS, &mDepthBufferResolution);
1233 
1234   // Set the viewport to be the entire window
1235   glViewport(0, 0, mWidth, mHeight);
1236 
1237   // Clear the buffer(s)
1238   glClear(GL_DEPTH_BUFFER_BIT);
1239 
1240   // Draw a gradient background
1241   glMatrixMode(GL_PROJECTION);
1242   glLoadIdentity();
1243   glMatrixMode(GL_MODELVIEW);
1244   glLoadIdentity();
1245   glDisable(GL_LIGHTING);
1246   glDisable(GL_DEPTH_TEST);
1247   glBegin(GL_QUADS);
1248   glColor3f(0.4f, 0.5f, 0.7f);
1249   glVertex3f(-1.0f, -1.0f, 0.5f);
1250   glColor3f(0.3f, 0.4f, 0.7f);
1251   glVertex3f(1.0f, -1.0f, 0.5f);
1252   glColor3f(0.1f, 0.1f, 0.2f);
1253   glVertex3f(1.0f, 1.0f, 0.5f);
1254   glColor3f(0.1f, 0.15f, 0.24f);
1255   glVertex3f(-1.0f, 1.0f, 0.5f);
1256   glEnd();
1257 
1258   // Calculate screen ratio (width / height)
1259   float ratio;
1260   if(mHeight == 0)
1261     ratio = 1.0f;
1262   else
1263     ratio = (float) mWidth / (float) mHeight;
1264 
1265   // Calculate optimal near and far Z clipping planes
1266   float farZ = (mAABBMax - mAABBMin).Abs() +
1267                (mCameraPosition - mCameraLookAt).Abs();
1268   if(farZ < 1e-20f)
1269     farZ = 1e-20f;
1270   float nearZ;
1271   if(mDepthBufferResolution >= 24)
1272     nearZ = 0.0001f * farZ;
1273   else
1274     nearZ = 0.01f * farZ;
1275 
1276   // Set up perspective projection
1277   glMatrixMode(GL_PROJECTION);
1278   glLoadIdentity();
1279   gluPerspective(60.0f, ratio, nearZ, farZ);
1280 
1281   // Set up the camera modelview matrix
1282   glMatrixMode(GL_MODELVIEW);
1283   glLoadIdentity();
1284   gluLookAt(mCameraPosition.x, mCameraPosition.y, mCameraPosition.z,
1285             mCameraLookAt.x, mCameraLookAt.y, mCameraLookAt.z,
1286             mCameraUp.x, mCameraUp.y, mCameraUp.z);
1287 
1288   // Read back camera matrices
1289   glGetDoublev(GL_MODELVIEW_MATRIX, mModelviewMatrix);
1290   glGetDoublev(GL_PROJECTION_MATRIX, mProjectionMatrix);
1291   glGetIntegerv(GL_VIEWPORT, mViewport);
1292 
1293   // Set up the lights
1294   SetupLighting();
1295 
1296   // Enable material shader
1297   if(mUseShader)
1298     glUseProgram(mShaderProgram);
1299   else
1300     glEnable(GL_LIGHTING);
1301 
1302   // Draw the mesh
1303   SetupMaterial();
1304   glEnable(GL_DEPTH_TEST);
1305   glPolygonMode(GL_FRONT_AND_BACK, mPolyMode);
1306   if(mTexHandle)
1307   {
1308     glBindTexture(GL_TEXTURE_2D, mTexHandle);
1309     glEnable(GL_TEXTURE_2D);
1310     glColor3f(1.0f, 1.0f, 1.0f);
1311   }
1312   else
1313     glColor3f(0.9f, 0.86f, 0.7f);
1314   if(mDisplayList)
1315     glCallList(mDisplayList);
1316   glDisable(GL_TEXTURE_2D);
1317   glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
1318 
1319   // Disable material shader
1320   if(mUseShader)
1321     glUseProgram(0);
1322   else
1323     glDisable(GL_LIGHTING);
1324 
1325   // Draw 2D overlay (information text etc)
1326   Draw2DOverlay();
1327 
1328   // Swap buffers
1329   glutSwapBuffers();
1330 
1331   // Focusing?
1332   if(mFocusing)
1333   {
1334     UpdateFocus();
1335     glutPostRedisplay();
1336   }
1337 }
1338 
1339 /// Resize function.
WindowResize(int w,int h)1340 void GLViewer::WindowResize(int w, int h)
1341 {
1342   // Store the new window size
1343   mWidth = w;
1344   mHeight = h;
1345 }
1346 
1347 /// Mouse click function
MouseClick(int button,int state,int x,int y)1348 void GLViewer::MouseClick(int button, int state, int x, int y)
1349 {
1350   bool clickConsumed = false;
1351   if(button == GLUT_LEFT_BUTTON)
1352   {
1353     // Check if any of the GUI buttons were clicked
1354     for(list<GLButton *>::iterator b = mButtons.begin(); b != mButtons.end(); ++ b)
1355     {
1356       if((*b)->MouseClick(state, x, y))
1357         clickConsumed = true;
1358     }
1359     if(!clickConsumed)
1360     {
1361       if(state == GLUT_DOWN)
1362       {
1363         double now = mTimer.GetTime();
1364         if((now - mLastClickTime) < DOUBLE_CLICK_TIME)
1365         {
1366           // Double click occured
1367           Vector3 mouseCoord3D;
1368           if(WinCoordTo3DCoord(x, y, mouseCoord3D))
1369           {
1370             mFocusStartTime = now;
1371             mFocusEndTime = now + FOCUS_TIME;
1372             mFocusStartPos = mCameraLookAt;
1373             mFocusEndPos = mouseCoord3D;
1374             mFocusStartDistance = (mCameraLookAt - mCameraPosition).Abs();
1375             mFocusEndDistance = mFocusStartDistance;
1376             mFocusing = true;
1377           }
1378           mLastClickTime = -1000.0;
1379         }
1380         else
1381         {
1382           // Single click occured
1383           mMouseRotate = true;
1384           mLastClickTime = now;
1385         }
1386       }
1387       else if(state == GLUT_UP)
1388         mMouseRotate = false;
1389     }
1390   }
1391   else if(button == GLUT_MIDDLE_BUTTON)
1392   {
1393     if(state == GLUT_DOWN)
1394       mMouseZoom = true;
1395     else if(state == GLUT_UP)
1396       mMouseZoom = false;
1397   }
1398   else if(button == GLUT_RIGHT_BUTTON)
1399   {
1400     if(state == GLUT_DOWN)
1401       mMousePan = true;
1402     else if(state == GLUT_UP)
1403       mMousePan = false;
1404   }
1405   else if(button == 3) // Mouse wheel up on some systems
1406   {
1407     if(state == GLUT_DOWN)
1408       ActionZoomIn();
1409   }
1410   else if(button == 4) // Mouse wheel down on some systems
1411   {
1412     if(state == GLUT_DOWN)
1413       ActionZoomOut();
1414   }
1415   mOldMouseX = x;
1416   mOldMouseY = y;
1417 
1418   // Focusing?
1419   if(mFocusing)
1420   {
1421     UpdateFocus();
1422     glutPostRedisplay();
1423   }
1424 }
1425 
1426 /// Mouse move function
MouseMove(int x,int y)1427 void GLViewer::MouseMove(int x, int y)
1428 {
1429   bool needsRedraw = false;
1430 
1431   float deltaX = (float) x - (float) mOldMouseX;
1432   float deltaY = (float) y - (float) mOldMouseY;
1433   mOldMouseX = x;
1434   mOldMouseY = y;
1435 
1436   if(mMouseRotate)
1437   {
1438     // Calculate delta angles
1439     float scale = 3.0f;
1440     if(mHeight > 0)
1441       scale /= (float) mHeight;
1442     float deltaTheta = -scale * deltaX;
1443     float deltaPhi = -scale * deltaY;
1444 
1445     // Adjust camera angles
1446     Vector3 viewVector = mCameraPosition - mCameraLookAt;
1447     float r = sqrtf(viewVector.x * viewVector.x +
1448                     viewVector.y * viewVector.y +
1449                     viewVector.z * viewVector.z);
1450     float phi, theta;
1451     if(r > 1e-20f)
1452     {
1453       if(mCameraUp.z > 0.0f)
1454       {
1455         phi = acosf(viewVector.z / r);
1456         theta = atan2f(viewVector.y, viewVector.x);
1457       }
1458       else
1459       {
1460         phi = acosf(viewVector.y / r);
1461         theta = atan2f(-viewVector.z, viewVector.x);
1462       }
1463     }
1464     else
1465     {
1466       if(mCameraUp.z > 0.0f)
1467         phi = viewVector.z > 0.0f ? 0.05f * PI : 0.95f * PI;
1468       else
1469         phi = viewVector.y > 0.0f ? 0.05f * PI : 0.95f * PI;
1470       theta = 0.0f;
1471     }
1472     phi += deltaPhi;
1473     theta += deltaTheta;
1474     if(phi > (0.95f * PI))
1475       phi = 0.95f * PI;
1476     else if(phi < (0.05f * PI))
1477       phi = 0.05f * PI;
1478 
1479     // Update the camera position
1480     if(mCameraUp.z > 0.0f)
1481     {
1482       viewVector.x = r * cos(theta) * sin(phi);
1483       viewVector.y = r * sin(theta) * sin(phi);
1484       viewVector.z = r * cos(phi);
1485     }
1486     else
1487     {
1488       viewVector.x = r * cos(theta) * sin(phi);
1489       viewVector.y = r * cos(phi);
1490       viewVector.z = -r * sin(theta) * sin(phi);
1491     }
1492     mCameraPosition = mCameraLookAt + viewVector;
1493 
1494     needsRedraw = true;
1495   }
1496   else if(mMouseZoom)
1497   {
1498     // Calculate delta angles
1499     float scale = 3.0f;
1500     if(mHeight > 0)
1501       scale /= (float) mHeight;
1502     float zoom = scale * deltaY;
1503 
1504     // Adjust camera zoom
1505     Vector3 viewVector = mCameraPosition - mCameraLookAt;
1506     viewVector = viewVector * powf(2.0f, zoom);
1507 
1508     // Update the camera position
1509     mCameraPosition = mCameraLookAt + viewVector;
1510 
1511     needsRedraw = true;
1512   }
1513   else if(mMousePan)
1514   {
1515     // Calculate delta movement
1516     float scale = 1.0f * (mCameraPosition - mCameraLookAt).Abs();
1517     if(mHeight > 0)
1518       scale /= (float) mHeight;
1519     float panX = scale * deltaX;
1520     float panY = scale * deltaY;
1521 
1522     // Calculate camera movement
1523     Vector3 viewDir = Normalize(mCameraPosition - mCameraLookAt);
1524     Vector3 rightDir = Normalize(Cross(viewDir, mCameraUp));
1525     Vector3 upDir = Normalize(Cross(rightDir, viewDir));
1526     Vector3 moveDelta = rightDir * panX + upDir * panY;
1527 
1528     // Update the camera position
1529     mCameraPosition += moveDelta;
1530     mCameraLookAt += moveDelta;
1531 
1532     needsRedraw = true;
1533   }
1534   else
1535   {
1536     // Call mouse move for all the GUI buttons
1537     for(list<GLButton *>::iterator b = mButtons.begin(); b != mButtons.end(); ++ b)
1538     {
1539       if((*b)->MouseMove(x, y))
1540         needsRedraw = true;
1541     }
1542   }
1543 
1544   // Redraw?
1545   if(needsRedraw)
1546     glutPostRedisplay();
1547 }
1548 
1549 /// Keyboard function
KeyDown(unsigned char key,int x,int y)1550 void GLViewer::KeyDown(unsigned char key, int x, int y)
1551 {
1552   if(key == 15)       // CTRL+O
1553     ActionOpenFile();
1554   else if(key == 19)  // CTRL+S
1555     ActionSaveFile();
1556   else if(key == 'w')
1557     ActionToggleWireframe();
1558   else if(key == 'f')
1559     ActionFitToScreen();
1560   else if(key == 'y')
1561     ActionCameraUpY();
1562   else if(key == 'z')
1563     ActionCameraUpZ();
1564   else if(key == '+')
1565     ActionZoomIn();
1566   else if(key == '-')
1567     ActionZoomOut();
1568   else if(key == 27)  // ESC
1569     ActionExit();
1570 }
1571 
1572 /// Keyboard function (special keys)
SpecialKeyDown(int key,int x,int y)1573 void GLViewer::SpecialKeyDown(int key, int x, int y)
1574 {
1575   if(key == GLUT_KEY_F1)
1576     ActionHelp();
1577 }
1578 
1579 
1580 //-----------------------------------------------------------------------------
1581 // Application main code
1582 //-----------------------------------------------------------------------------
1583 
1584 /// Constructor
GLViewer()1585 GLViewer::GLViewer()
1586 {
1587   // Clear internal state
1588   mFileName = "";
1589   mFilePath = "";
1590   mFileSize = 0;
1591   mWidth = 1;
1592   mHeight = 1;
1593   mDepthBufferResolution = 16;
1594   mOldMouseX = 0;
1595   mOldMouseY = 0;
1596   mMouseRotate = false;
1597   mMouseZoom = false;
1598   mMousePan = false;
1599   mCameraUp = Vector3(0.0f, 0.0f, 1.0f);
1600   mFocusStartPos = Vector3(0.0f, 0.0f, 0.0f);
1601   mFocusEndPos = Vector3(0.0f, 0.0f, 0.0f);
1602   mFocusStartTime = 0.0;
1603   mFocusEndTime = 0.0;
1604   mFocusStartDistance = 1.0;
1605   mFocusEndDistance = 1.0;
1606   mFocusing = false;
1607   mLastClickTime = -1000.0;
1608   mDisplayList = 0;
1609   mPolyMode = GL_FILL;
1610   mTexHandle = 0;
1611   mUseShader = false;
1612   mShaderProgram = 0;
1613   mVertShader = 0;
1614   mFragShader = 0;
1615   mMesh = NULL;
1616 }
1617 
1618 /// Destructor
~GLViewer()1619 GLViewer::~GLViewer()
1620 {
1621   // Free all GUI buttons
1622   for(list<GLButton *>::iterator b = mButtons.begin(); b != mButtons.end(); ++ b)
1623     delete (*b);
1624 
1625   // Free the mesh
1626   if(mMesh)
1627     delete mMesh;
1628 }
1629 
1630 /// Run the application
Run(int argc,char ** argv)1631 void GLViewer::Run(int argc, char **argv)
1632 {
1633   try
1634   {
1635     // Init GLUT
1636     glutInit(&argc, argv);
1637 
1638     // Create the glut window
1639     glutInitWindowSize(640, 480);
1640     glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
1641     glutCreateWindow("OpenCTM viewer");
1642 
1643     // Init GLEW (for OpenGL 2.x support)
1644     if(glewInit() != GLEW_OK)
1645       throw runtime_error("Unable to initialize GLEW.");
1646 
1647     // Load the phong shader, if we can
1648     if(GLEW_VERSION_2_0)
1649       InitShader();
1650     else if(GLEW_VERSION_1_2)
1651       glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
1652 
1653     // Set the GLUT callback functions (these are bridged to the corresponding
1654     // class methods)
1655     glutReshapeFunc(GLUTWindowResize);
1656     glutDisplayFunc(GLUTWindowRedraw);
1657     glutMouseFunc(GLUTMouseClick);
1658     glutMotionFunc(GLUTMouseMove);
1659     glutPassiveMotionFunc(GLUTMouseMove);
1660     glutKeyboardFunc(GLUTKeyDown);
1661     glutSpecialFunc(GLUTSpecialKeyDown);
1662 
1663     // Create GUI buttons
1664     GLButton * b1 = new OpenButton();
1665     mButtons.push_back(b1);
1666     b1->mParent = this;
1667     b1->SetGlyph(icon_open, 32, 32, 4);
1668     b1->mPosX = 12;
1669     b1->mPosY = 10;
1670     GLButton * b2 = new SaveButton();
1671     mButtons.push_back(b2);
1672     b2->mParent = this;
1673     b2->SetGlyph(icon_save, 32, 32, 4);
1674     b2->mPosX = 60;
1675     b2->mPosY = 10;
1676     GLButton * b3 = new OpenTextureButton();
1677     mButtons.push_back(b3);
1678     b3->mParent = this;
1679     b3->SetGlyph(icon_texture, 32, 32, 4);
1680     b3->mPosX = 108;
1681     b3->mPosY = 10;
1682     GLButton * b4 = new HelpButton();
1683     mButtons.push_back(b4);
1684     b4->mParent = this;
1685     b4->SetGlyph(icon_help, 32, 32, 4);
1686     b4->mPosX = 156;
1687     b4->mPosY = 10;
1688 
1689     // Load the file
1690     if(argc >= 2)
1691     {
1692       const char * overrideTexName = NULL;
1693       if(argc >= 3)
1694         overrideTexName = argv[2];
1695       LoadFile(argv[1], overrideTexName);
1696     }
1697 
1698     // Enter the main loop
1699     glutMainLoop();
1700   }
1701   catch(ctm_error &e)
1702   {
1703     SysMessageBox mb;
1704     mb.mMessageType = SysMessageBox::mtError;
1705     mb.mCaption = "Error";
1706     mb.mText = string("OpenCTM error: ") + string(e.what());
1707     mb.Show();
1708   }
1709   catch(exception &e)
1710   {
1711     SysMessageBox mb;
1712     mb.mMessageType = SysMessageBox::mtError;
1713     mb.mCaption = "Error";
1714     mb.mText = string(e.what());
1715     mb.Show();
1716   }
1717   cout << endl;
1718 }
1719 
1720 
1721 //-----------------------------------------------------------------------------
1722 // Bridge GLUT callback functions to class methods
1723 //-----------------------------------------------------------------------------
1724 
1725 // NOTE: This is just a hack to be able to reference the application class
1726 // object from the GLUT callback functions, since there is no way (afaik) to
1727 // pass user data (i.e. the object reference) through GLUT...
1728 static GLViewer * gGLViewer = NULL;
1729 
1730 /// Redraw function.
GLUTWindowRedraw(void)1731 void GLUTWindowRedraw(void)
1732 {
1733   if(gGLViewer)
1734     gGLViewer->WindowRedraw();
1735 }
1736 
1737 /// Resize function.
GLUTWindowResize(int w,int h)1738 void GLUTWindowResize(int w, int h)
1739 {
1740   if(gGLViewer)
1741     gGLViewer->WindowResize(w, h);
1742 }
1743 
1744 /// Mouse click function
GLUTMouseClick(int button,int state,int x,int y)1745 void GLUTMouseClick(int button, int state, int x, int y)
1746 {
1747   if(gGLViewer)
1748     gGLViewer->MouseClick(button, state, x, y);
1749 }
1750 
1751 /// Mouse move function
GLUTMouseMove(int x,int y)1752 void GLUTMouseMove(int x, int y)
1753 {
1754   if(gGLViewer)
1755     gGLViewer->MouseMove(x, y);
1756 }
1757 
1758 /// Keyboard function
GLUTKeyDown(unsigned char key,int x,int y)1759 void GLUTKeyDown(unsigned char key, int x, int y)
1760 {
1761   if(gGLViewer)
1762     gGLViewer->KeyDown(key, x, y);
1763 }
1764 
1765 /// Keyboard function (special keys)
GLUTSpecialKeyDown(int key,int x,int y)1766 void GLUTSpecialKeyDown(int key, int x, int y)
1767 {
1768   if(gGLViewer)
1769     gGLViewer->SpecialKeyDown(key, x, y);
1770 }
1771 
1772 
1773 //-----------------------------------------------------------------------------
1774 // Program startup
1775 //-----------------------------------------------------------------------------
1776 
1777 /// Program entry.
main(int argc,char ** argv)1778 int main(int argc, char **argv)
1779 {
1780   // Run the application class
1781   gGLViewer = new GLViewer;
1782   gGLViewer->Run(argc, argv);
1783   delete gGLViewer;
1784   gGLViewer = NULL;
1785 
1786   return 0;
1787 }
1788