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