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