1 /**************************************************************************/
2 /*  Copyright 2009 Tim Day                                                */
3 /*                                                                        */
4 /*  This file is part of Fracplanet                                       */
5 /*                                                                        */
6 /*  Fracplanet is free software: you can redistribute it and/or modify    */
7 /*  it under the terms of the GNU General Public License as published by  */
8 /*  the Free Software Foundation, either version 3 of the License, or     */
9 /*  (at your option) any later version.                                   */
10 /*                                                                        */
11 /*  Fracplanet is distributed in the hope that it will be useful,         */
12 /*  but WITHOUT ANY WARRANTY; without even the implied warranty of        */
13 /*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         */
14 /*  GNU General Public License for more details.                          */
15 /*                                                                        */
16 /*  You should have received a copy of the GNU General Public License     */
17 /*  along with Fracplanet.  If not, see <http://www.gnu.org/licenses/>.   */
18 /**************************************************************************/
19 
20 #include "triangle_mesh_viewer_display.h"
21 
22 #include "matrix33.h"
23 #include "triangle_mesh_viewer.h"
24 
25 #include <GL/glu.h>
26 
TriangleMeshViewerDisplay(TriangleMeshViewer * parent,const QGLFormat & format,const ParametersRender * param,const std::vector<const TriangleMesh * > & m,bool verbose)27 TriangleMeshViewerDisplay::TriangleMeshViewerDisplay(TriangleMeshViewer* parent,const QGLFormat& format,const ParametersRender* param,const std::vector<const TriangleMesh*>& m,bool verbose)
28   :QGLWidget(format,parent)
29   ,_notify(*parent)
30   ,_verbose(verbose)
31   ,mesh(m)
32   ,parameters(param)
33   ,gl_display_list_index(0)
34   ,frame_number(0)
35   ,width(0)
36   ,height(0)
37   ,frame_time()
38   ,camera_position(3.0f,0.0f,0.0f)
39   ,camera_lookat(0.0f,0.0f,0.0f)
40   ,camera_up(0.0f,0.0f,1.0f)
41   ,object_tilt(30.0f*M_PI/180.0f)
42   ,object_rotation(0.0f)
43 {
44   assert(isValid());
45 
46   setSizePolicy(QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding));
47 
48   frame_time.start();
49   frame_time_reported.start();
50 }
51 
~TriangleMeshViewerDisplay()52 TriangleMeshViewerDisplay::~TriangleMeshViewerDisplay()
53 {
54   makeCurrent();
55   if (gl_display_list_index)
56     glDeleteLists(gl_display_list_index,1);
57 }
58 
minimumSizeHint() const59 QSize TriangleMeshViewerDisplay::minimumSizeHint() const
60 {
61   return QSize(64,64);
62 }
63 
sizeHint() const64 QSize TriangleMeshViewerDisplay::sizeHint() const
65 {
66   return QSize(512,512);
67 }
68 
set_mesh(const std::vector<const TriangleMesh * > & m)69 void TriangleMeshViewerDisplay::set_mesh(const std::vector<const TriangleMesh*>& m)
70 {
71   mesh=m;
72 
73   // If there is a display list allocated for the previous mesh, delete it.
74   if (gl_display_list_index!=0)
75     {
76       glDeleteLists(gl_display_list_index,1);
77       gl_display_list_index=0;
78     }
79 }
80 
background_colour() const81 const FloatRGBA TriangleMeshViewerDisplay::background_colour() const
82 {
83   if (mesh.empty()) return FloatRGBA(0.0f,0.0f,0.0f,1.0f);
84 
85   const XYZ relative_camera_position
86     =Matrix33RotateAboutZ(-object_rotation)*Matrix33RotateAboutX(-object_tilt)*camera_position;
87 
88   const float h = mesh[0]->geometry().height(relative_camera_position);
89   if (h<=0.0f) return parameters->background_colour_low;
90   else if (h>=1.0f) return parameters->background_colour_high;
91   else return parameters->background_colour_low+h*(parameters->background_colour_high-parameters->background_colour_low);
92 }
93 
check_for_gl_errors(const char * where) const94 void TriangleMeshViewerDisplay::check_for_gl_errors(const char* where) const
95 {
96   GLenum error;
97   while ((error=glGetError())!=GL_NO_ERROR)
98     {
99       std::ostringstream msg;
100       msg << "GL error in " << where << " (frame " << frame_number << ") : ";
101       switch (error)
102 	{
103 	case GL_INVALID_ENUM:
104 	  msg << "GL_INVALID_ENUM";
105 	  break;
106 	case GL_INVALID_VALUE:
107 	  msg << "GL_INVALID_VALUE";
108 	  break;
109 	case GL_INVALID_OPERATION:
110 	  msg << "GL_INVALID_OPERATION";
111 	  break;
112 	case GL_STACK_OVERFLOW:
113 	  msg << "GL_STACK_OVERFLOW";
114 	  break;
115 	case GL_STACK_UNDERFLOW:
116 	  msg << "GL_STACK_UNDERFLOW";
117 	  break;
118 	case GL_OUT_OF_MEMORY:
119 	  msg << "GL_OUT_OF_MEMORY";
120 	  break;
121 	}
122       std::cerr << msg.str() << std::endl;
123     }
124 }
125 
paintGL()126 void TriangleMeshViewerDisplay::paintGL()
127 {
128   assert(isValid());
129 
130   const FloatRGBA bg=background_colour();
131   glClearColor(bg.r,bg.g,bg.b,1.0f);
132   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
133 
134   const float a=parameters->ambient;
135 
136   GLfloat global_ambient[]={a,a,a,1.0};
137   glLightModelfv(GL_LIGHT_MODEL_AMBIENT,global_ambient);
138 
139   GLfloat light_diffuse[]={1.0f-a,1.0f-a,1.0f-a,1.0};
140   glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
141 
142   glMatrixMode(GL_MODELVIEW);
143   glLoadIdentity();
144 
145   gluLookAt(
146 	    camera_position.x,camera_position.y,camera_position.z,
147 	    camera_lookat.x  ,camera_lookat.y  ,camera_lookat.z,
148 	    camera_up.x      ,camera_up.y      ,camera_up.z
149 	    );
150 
151   const XYZ light_direction(parameters->illumination_direction());
152   GLfloat light_position[]=
153     {
154       light_direction.x,
155       light_direction.y,
156       light_direction.z,
157       0.0f     // w=0 implies directional light
158     };
159 
160   glLightfv(GL_LIGHT0,GL_POSITION,light_position);
161 
162   glRotatef((180.0/M_PI)*object_tilt,1.0,0.0,0.0);
163   glRotatef((180.0/M_PI)*object_rotation,0.0,0.0,1.0);
164 
165   glPolygonMode(GL_FRONT_AND_BACK,(parameters->wireframe ? GL_LINE : GL_FILL));
166 
167   glEnable(GL_CULL_FACE);
168   glFrontFace(GL_CCW);
169 
170   if (parameters->display_list && gl_display_list_index!=0)
171     {
172       glCallList(gl_display_list_index);
173     }
174   else
175     {
176       bool building_display_list=(parameters->display_list && gl_display_list_index==0);
177 
178       if (building_display_list)
179 	{
180 	  gl_display_list_index=glGenLists(1);
181 	  assert(gl_display_list_index!=0);
182 	  glNewList(gl_display_list_index,GL_COMPILE_AND_EXECUTE);
183 	  if (_verbose) std::cerr << "Building display list...";
184 	}
185 
186       GLfloat default_material_white[3]={1.0f,1.0f,1.0f};
187       GLfloat default_material_black[3]={0.0f,0.0f,0.0f};
188       glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,default_material_white);
189       glMaterialfv(GL_FRONT_AND_BACK,GL_EMISSION,default_material_black);
190 
191       for (uint m=0;m<mesh.size();m++)
192 	{
193 	  const TriangleMesh*const it=mesh[m];
194 	  if (it==0) continue;
195 
196 	  // Meshes after the first are rendered twice: first the backfacing polys then the front facing.
197 	  // This solves the problem of either clouds disappearing when we're under them (with backface culling)
198 	  // or weird stuff around the periphery when culling is on.
199 	  // It's quite an expensive solution!
200 	  const uint passes=(m==0 ? 1 : 2);
201 	  for (uint pass=0;pass<passes;pass++)
202 	    {
203 	      if (passes==2 && pass==0)
204 		{
205 		  glCullFace(GL_FRONT);
206 		}
207 	      else
208 		{
209 		  glCullFace(GL_BACK);
210 		}
211 
212 	      if (it->emissive()==0.0f)
213 		{
214 		  if (m) // Switch blending on for non-emissive meshes after the first
215 		    {
216 		      glEnable(GL_BLEND);
217 		      glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
218 		    }
219 		  else
220 		    {
221 		      glDisable(GL_BLEND);
222 		    }
223 
224 		  // Use "Color Material" mode 'cos everything is the same material.... just change the colour
225 		  glEnable(GL_COLOR_MATERIAL);
226 		  glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
227 
228 		  // Point GL at arrays of data
229 		  glVertexPointer(3,GL_FLOAT,sizeof(Vertex),&(it->vertex(0).position().x));
230 		  glNormalPointer(GL_FLOAT,sizeof(Vertex),&(it->vertex(0).normal().x));
231 
232 		  // For a second mesh, use alpha (actually could use it for the first mesh but maybe it's more efficient not to).
233 		  glColorPointer((m==0 ? 3 : 4),GL_UNSIGNED_BYTE,sizeof(Vertex),&(it->vertex(0).colour(0).r));
234 
235 		  // Builds on some platforms (ie Ubuntu) seem to get in a mess if you render >1k primitives
236 		  // (3k vertices).  NB This is a problem in the client; not the xserver.
237 		  // Debian (Sarge or Etch) has no problems with unlimited batches.
238 		  // Note it's simply the batch size; there doesn't seem to be any problem with the 10Ks of vertices.
239 		  // Since the limited batch size doesn't seem to hurt working implementations we just use it everywhere.
240 		  const uint batch_size=1000;
241 
242 		  // Draw the colour-zero triangles
243 		  for (uint t=0;t<it->triangles_of_colour0();t+=batch_size)
244 		    {
245 		      glDrawRangeElements
246 			(
247 			 GL_TRIANGLES,
248 			 0,
249 			 it->vertices()-1,
250 			 3*std::min(batch_size,static_cast<uint>(it->triangles_of_colour0()-t)),
251 			 GL_UNSIGNED_INT,
252 			 &(it->triangle(t).vertex(0))
253 			 );
254 		      if (_verbose && building_display_list)
255 			{
256 			  std::cerr << ".";
257 			}
258 		    }
259 
260 		  // Switch to alternate colour and draw the colour-one triangles
261 		  glColorPointer(3,GL_UNSIGNED_BYTE,sizeof(Vertex),&(it->vertex(0).colour(1).r));
262 
263 		  for (uint t=it->triangles_of_colour0();t<it->triangles();t+=batch_size)
264 		    {
265 		      glDrawRangeElements
266 			(
267 			 GL_TRIANGLES,
268 			 0,
269 			 it->vertices()-1,
270 			 3*std::min(batch_size,static_cast<uint>(it->triangles()-t)),
271 			 GL_UNSIGNED_INT,
272 			 &(it->triangle(t).vertex(0))
273 			 );
274 		      if (_verbose && building_display_list)
275 			{
276 			  std::cerr << ".";
277 			}
278 		    }
279 
280 		  glDisable(GL_COLOR_MATERIAL);
281 		}
282 	      else // implies mesh[m]->emissive()>0.0
283 		{
284 		  // We abuse alpha for emission, so no blending
285 		  glDisable(GL_BLEND);
286 
287 		  // If there could be emissive vertices, we need to do things the hard way
288 		  // using immediate mode.  Maybe the display list capture will help.
289 
290 		  const float k=1.0f/255.0f;
291 		  const float em=k*(     it->emissive());
292 		  const float ad=k*(1.0f-it->emissive());
293 
294 		  glBegin(GL_TRIANGLES);
295 		  for (unsigned int t=0;t<it->triangles();t++)
296 		    {
297 		      if (_verbose && building_display_list && (t&0x3ff) == 0)
298 			{
299 			  std::cerr << ".";
300 			}
301 
302 		      const uint c=(t<it->triangles_of_colour0() ? 0 : 1);
303 		      for (uint i=0;i<3;i++)
304 			{
305 			  const uint vn=it->triangle(t).vertex(i);
306 			  const Vertex& v=it->vertex(vn);
307 
308 			  GLfloat c_ad[3];
309 			  GLfloat c_em[3];
310 			  if (v.colour(c).a==0)  // Zero alpha used to imply emissive vertex colour
311 			    {
312 			      c_ad[0]=v.colour(c).r*ad;
313 			      c_ad[1]=v.colour(c).g*ad;
314 			      c_ad[2]=v.colour(c).b*ad;
315 			      c_em[0]=v.colour(c).r*em;
316 			      c_em[1]=v.colour(c).g*em;
317 			      c_em[2]=v.colour(c).b*em;
318 			    }
319 			  else
320 			    {
321 			      c_ad[0]=v.colour(c).r*k;
322 			      c_ad[1]=v.colour(c).g*k;
323 			      c_ad[2]=v.colour(c).b*k;
324 			      c_em[0]=0.0f;
325 			      c_em[1]=0.0f;
326 			      c_em[2]=0.0f;
327 			    }
328 
329 			  glNormal3fv(&(v.normal().x));
330 			  glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,c_ad);
331 			  glMaterialfv(GL_FRONT_AND_BACK,GL_EMISSION,c_em);
332 			  glVertex3fv(&(v.position().x));
333 			}
334 		    }
335 		  glEnd();
336 		}
337 	    }
338 	}
339 
340       if (building_display_list)
341 	{
342 	  glEndList();
343 	  if (_verbose)
344 	    {
345 	      std::cerr << "\n...built display list\n";
346 	    }
347 	}
348     }
349 
350   if (_verbose)
351     check_for_gl_errors(__PRETTY_FUNCTION__);
352 
353   // Get time taken since last frame
354   const uint dt=frame_time.restart();
355 
356   // Save it in the queue
357   frame_times.push_back(dt);
358 
359   // Keep last 30 frame times
360   while (frame_times.size()>30) frame_times.pop_front();
361 
362   // Only update frame time a couple of times a second to reduce flashing
363   if (frame_time_reported.elapsed()>500)
364     {
365       //! \todo Frame time calculation is wrong... need -1 correction to number of frames
366       const float average_time=std::accumulate
367 	(
368 	 frame_times.begin(),
369 	 frame_times.end(),
370 	 0
371 	 )/static_cast<float>(frame_times.size());
372 
373       const float fps=1000.0/average_time;
374 
375       std::ostringstream report;
376       report.setf(std::ios::fixed);
377       report.precision(1);
378 
379       uint n_triangles=0;
380       uint n_vertices=0;
381       for (uint m=0;m<mesh.size();m++)
382 	{
383 	  if (mesh[m])
384 	    {
385 	      n_triangles+=mesh[m]->triangles();
386 	      n_vertices+=mesh[m]->vertices();
387 	    }
388 	}
389       report
390 	<< "Triangles: "
391 	<< n_triangles
392 	<< ", "
393 	<< "Vertices: "
394 	<< n_vertices
395 	<< ", "
396 	<< "FPS: "
397 	<< fps
398 	<< "\n";
399 
400       _notify.notify(report.str());
401       frame_time_reported.restart();
402     }
403 }
404 
initializeGL()405 void TriangleMeshViewerDisplay::initializeGL()
406 {
407   if (_verbose)
408     {
409       std::cerr << "Double buffering " << (doubleBuffer() ? "ON" : "OFF") << "\n";
410       std::cerr << "Auto Buffer Swap " << (autoBufferSwap() ? "ON" : "OFF") << "\n";
411       std::cerr << "Multisampling    " << (format().sampleBuffers() ? "ON" : "OFF") << "\n";
412     }
413 
414   const FloatRGBA bg=background_colour();
415   glClearColor(bg.r,bg.g,bg.b,1.0f);
416 
417   // Switch depth-buffering on
418   glEnable(GL_DEPTH_TEST);
419 
420   // Basic lighting stuff (set ambient globally rather than in light)
421   GLfloat black_light[]={0.0,0.0,0.0,1.0};
422   glLightfv(GL_LIGHT0,GL_AMBIENT,black_light);
423   glLightfv(GL_LIGHT0,GL_SPECULAR,black_light);
424   glEnable(GL_LIGHT0);
425   glEnable(GL_LIGHTING);
426 
427   // Do smooth shading 'cos colours are specified at vertices
428   glShadeModel(GL_SMOOTH);
429 
430   // Use arrays of data
431   glEnableClientState(GL_VERTEX_ARRAY);
432   glEnableClientState(GL_COLOR_ARRAY);
433   glEnableClientState(GL_NORMAL_ARRAY);
434 }
435 
resizeGL(int w,int h)436 void TriangleMeshViewerDisplay::resizeGL(int w,int h)
437 {
438   width=w;
439   height=h;
440 
441   if (_verbose)
442     std::cerr << "QGLWidget resized to " << width << "x" << height << std::endl;
443 
444   glViewport(0,0,static_cast<GLint>(w),static_cast<GLint>(h));
445   glMatrixMode(GL_PROJECTION);
446   glLoadIdentity();
447 
448   // View angle is specified in vertical direction, but we need to exaggerate it if image is taller than wide.
449   const float view_angle_degrees=minimum(90.0,width>height ? 45.0 : 45.0*height/width);
450 
451   gluPerspective
452     (
453      view_angle_degrees,
454      static_cast<float>(width)/static_cast<float>(height),
455      0.01,10.0
456      );  // Was 0.1 (too far); 0.001 gives artefacts
457 
458   glMatrixMode(GL_MODELVIEW);
459   glLoadIdentity();
460 }
461 
draw_frame(const XYZ & p,const XYZ & l,const XYZ & u,float r,float t)462 void TriangleMeshViewerDisplay::draw_frame(const XYZ& p,const XYZ& l,const XYZ& u,float r,float t)
463 {
464   frame_number++;
465 
466   camera_position=p;
467   camera_lookat=l;
468   camera_up=u;
469   object_rotation=r;
470   object_tilt=t;
471 
472   updateGL();
473 }
474