1 /*
2 * The 3D Studio File Format Library
3 * Copyright (C) 1996-2007 by Jan Eric Kyprianidis <www.lib3ds.org>
4 * All rights reserved.
5 *
6 * This program is  free  software;  you can redistribute it and/or modify it
7 * under the terms of the  GNU Lesser General Public License  as published by
8 * the  Free Software Foundation;  either version 2.1 of the License,  or (at
9 * your option) any later version.
10 *
11 * This  program  is  distributed in  the  hope that it will  be useful,  but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or  FITNESS FOR A  PARTICULAR PURPOSE.  See the  GNU Lesser General Public
14 * License for more details.
15 *
16 * You should  have received  a copy of the GNU Lesser General Public License
17 * along with  this program;  if not, write to the  Free Software Foundation,
18 * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 *
20 * $Id: 3dsplay.c,v 1.14 2007/06/18 06:51:53 jeh Exp $
21 */
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 #include <lib3ds/file.h>
28 #include <lib3ds/camera.h>
29 #include <lib3ds/mesh.h>
30 #include <lib3ds/node.h>
31 #include <lib3ds/material.h>
32 #include <lib3ds/matrix.h>
33 #include <lib3ds/vector.h>
34 #include <lib3ds/light.h>
35 #include <string.h>
36 #include <stdlib.h>
37 #include <math.h>
38 
39 // OS X has a different path than everyone else
40 #ifdef __APPLE__
41 #include <GLUT/glut.h>
42 #else
43 #include <GL/glut.h>
44 #endif
45 
46 #ifdef	USE_SDL
47 #include <SDL_image.h>
48 #endif
49 
50 
51 
52 
53 #define	MOUSE_SCALE	.1	/* degrees/pixel movement */
54 
55 /*!
56 \example player.c
57 
58 Previews a <i>3DS</i> file using OpenGL.
59 
60 \code
61 Syntax: player filename
62 \endcode
63 
64 \warning To compile this program you must have OpenGL and glut installed.
65 */
66 
67 
68 typedef	enum {ROTATING, WALKING} RunMode;
69 
70 static	RunMode runMode = ROTATING;
71 
72 static const char* filepath=NULL;
73 static char datapath[256];
74 static char filename[256];
75 static int dbuf=1;
76 static int halt=0;
77 static int flush=0;
78 static int anti_alias=1;
79 
80 static const char* camera=0;
81 static Lib3dsFile *file=0;
82 static float current_frame=0.0;
83 static int gl_width;
84 static int gl_height;
85 static int menu_id=0;
86 static int show_object=1;
87 static int show_bounds=0;
88 static int rotating = 0;
89 static int show_cameras = 0;
90 static int show_lights = 0;
91 
92 static int cameraList, lightList;	/* Icon display lists */
93 
94 static Lib3dsVector bmin, bmax;
95 static float	sx, sy, sz, size;	/* bounding box dimensions */
96 static float	cx, cy, cz;		/* bounding box center */
97 
98 static	float	view_rotx = 0., view_roty = 0., view_rotz = 0.;
99 static	float	anim_rotz = 0.;
100 
101 static	int	mx, my;
102 
103 static const GLfloat white[4] = {1.,1.,1.,1.};
104 static const GLfloat dgrey[4] = {.25,.25,.25,1.};
105 static const GLfloat grey[4] = {.5,.5,.5,1.};
106 static const GLfloat lgrey[4] = {.75,.75,.75,1.};
107 static const GLfloat black[] = {0.,0.,0.,1.};
108 static const GLfloat red[4] = {1.,0.,0.,1.};
109 static const GLfloat green[4] = {0.,1.,0.,1.};
110 static const GLfloat blue[4] = {0.,0.,1.,1.};
111 
112 
113 static	void	solidBox(double bx, double by, double bz);
114 static	void	solidCylinder(double r, double h, int slices);
115 static	int	callback(void (*cb)(int m, int d, void *), void *client);
116 static	void	call_callback(int idx, int data);
117 
118 static void solidBox(double bx, double by, double bz);
119 static void solidCylinder(double r, double h, int slices);
120 static const char *Basename(const char *filename);
121 
122 
123 // texture size: by now minimum standard
124 #define	TEX_XSIZE	1024
125 #define	TEX_YSIZE	1024
126 
127 struct _player_texture
128 {
129   int valid; // was the loading attempt successful ?
130 #ifdef	USE_SDL
131   SDL_Surface *bitmap;
132 #else
133   void *bitmap;
134 #endif
135   GLuint tex_id; //OpenGL texture ID
136   float scale_x, scale_y; // scale the texcoords, as OpenGL thinks in TEX_XSIZE and TEX_YSIZE
137 };
138 
139 typedef struct _player_texture Player_texture;
140 Player_texture *pt;
141 int tex_mode; // Texturing active ?
142 
143 #define	NA(a)	(sizeof(a)/sizeof(a[0]))
144 
145 #ifndef	MIN
146 #define	MIN(a,b) ((a)<(b)?(a):(b))
147 #define	MAX(a,b) ((a)>(b)?(a):(b))
148 #endif
149 
150 
151 
152 
153 static	void
menu_cb(int value)154 menu_cb(int value)
155 {
156   call_callback(value, 0);
157 }
158 
159 
160 /*!
161 * Switch cameras based on user's menu choice.
162 */
163 static void
camera_menu(int menu,int value,void * client)164 camera_menu(int menu, int value, void *client)
165 {
166   Lib3dsCamera *c = (Lib3dsCamera*)client;
167   view_rotx = view_roty = view_rotz = anim_rotz = 0.;
168   camera=c->name;
169 }
170 
171 
172 /*!
173 * Toggle an arbitrary int (bool) variable
174 */
175 static	void
toggle_bool(int menu,int value,void * client)176 toggle_bool(int menu, int value, void *client)
177 {
178   int *var = client;
179   *var = !*var;
180   glutPostRedisplay();
181 }
182 
183 
184 
185 /*!
186 * Build the menu
187 */
188 static void
build_menu()189 build_menu()
190 {
191   Lib3dsCamera *c;
192   int i;
193   menu_id=glutCreateMenu(menu_cb);
194 
195   for (c=file->cameras,i=0; c; c=c->next,++i)
196     glutAddMenuEntry(c->name, callback(camera_menu, c));
197 
198   glutAddMenuEntry("Show cameras", callback(toggle_bool, &show_cameras));
199   glutAddMenuEntry("Show lights", callback(toggle_bool, &show_lights));
200   glutAddMenuEntry("Show bounds", callback(toggle_bool, &show_bounds));
201 }
202 
203 
204 /*!
205 * Time function, called every frame
206 */
207 static	void
timer_cb(int value)208 timer_cb(int value)
209 {
210   glutPostRedisplay();
211 
212   if (!halt) {
213     view_rotz += anim_rotz;
214     current_frame+=1.0;
215     if (current_frame>file->frames) {
216       current_frame=0;
217     }
218     lib3ds_file_eval(file, current_frame);
219     glutTimerFunc(10, timer_cb, 0);
220   }
221 }
222 
223 static	void
set_halt(int h)224 set_halt(int h)
225 {
226   if( h != halt ) {
227     halt = h;
228     if( !halt )
229       glutTimerFunc(10, timer_cb, 0);
230   }
231 }
232 
233 
234 
235 /*!
236 * Initialize OpenGL
237 */
238 static void
init(void)239 init(void)
240 {
241   glClearColor(0.5, 0.5, 0.5, 1.0);
242   glShadeModel(GL_SMOOTH);
243   glEnable(GL_LIGHTING);
244   glEnable(GL_LIGHT0);
245   glDisable(GL_LIGHT1);
246   glDepthFunc(GL_LEQUAL);
247   glEnable(GL_DEPTH_TEST);
248   glCullFace(GL_BACK);
249   //glDisable(GL_NORMALIZE);
250   //glPolygonOffset(1.0, 2);
251 }
252 
253 
254 /*!
255 * Load the model from .3ds file.
256 */
257 static void
load_model(void)258 load_model(void)
259 {
260   file=lib3ds_file_load(filepath);
261   if (!file) {
262     puts("3dsplayer: Error: Loading 3DS file failed.\n");
263     exit(1);
264   }
265 
266   /* No nodes?  Fabricate nodes to display all the meshes. */
267   if( !file->nodes )
268   {
269     Lib3dsMesh *mesh;
270     Lib3dsNode *node;
271 
272     for(mesh = file->meshes; mesh != NULL; mesh = mesh->next)
273     {
274       node = lib3ds_node_new_object();
275       strcpy(node->name, mesh->name);
276       node->parent_id = LIB3DS_NO_PARENT;
277       lib3ds_file_insert_node(file, node);
278     }
279   }
280 
281   lib3ds_file_eval(file, 1.0f);
282   lib3ds_file_bounding_box_of_nodes(file, LIB3DS_TRUE, LIB3DS_FALSE, LIB3DS_FALSE, bmin, bmax);
283   sx = bmax[0] - bmin[0];
284   sy = bmax[1] - bmin[1];
285   sz = bmax[2] - bmin[2];
286   size = MAX(sx, sy); size = MAX(size, sz);
287   cx = (bmin[0] + bmax[0])/2;
288   cy = (bmin[1] + bmax[1])/2;
289   cz = (bmin[2] + bmax[2])/2;
290 
291 
292   /* No cameras in the file?  Add four */
293 
294   if( !file->cameras ) {
295 
296     /* Add some cameras that encompass the bounding box */
297 
298     Lib3dsCamera *camera = lib3ds_camera_new("Camera_X");
299     camera->target[0] = cx;
300     camera->target[1] = cy;
301     camera->target[2] = cz;
302     memcpy(camera->position, camera->target, sizeof(camera->position));
303     camera->position[0] = bmax[0] + 1.5 * MAX(sy,sz);
304     camera->near_range = ( camera->position[0] - bmax[0] ) * .5;
305     camera->far_range = ( camera->position[0] - bmin[0] ) * 2;
306     lib3ds_file_insert_camera(file, camera);
307 
308     /* Since lib3ds considers +Y to be into the screen, we'll put
309     * this camera on the -Y axis, looking in the +Y direction.
310     */
311     camera = lib3ds_camera_new("Camera_Y");
312     camera->target[0] = cx;
313     camera->target[1] = cy;
314     camera->target[2] = cz;
315     memcpy(camera->position, camera->target, sizeof(camera->position));
316     camera->position[1] = bmin[1] - 1.5 * MAX(sx,sz);
317     camera->near_range = ( bmin[1] - camera->position[1] ) * .5;
318     camera->far_range = ( bmax[1] - camera->position[1] ) * 2;
319     lib3ds_file_insert_camera(file, camera);
320 
321     camera = lib3ds_camera_new("Camera_Z");
322     camera->target[0] = cx;
323     camera->target[1] = cy;
324     camera->target[2] = cz;
325     memcpy(camera->position, camera->target, sizeof(camera->position));
326     camera->position[2] = bmax[2] + 1.5 * MAX(sx,sy);
327     camera->near_range = ( camera->position[2] - bmax[2] ) * .5;
328     camera->far_range = ( camera->position[2] - bmin[2] ) * 2;
329     lib3ds_file_insert_camera(file, camera);
330 
331     camera = lib3ds_camera_new("Camera_ISO");
332     camera->target[0] = cx;
333     camera->target[1] = cy;
334     camera->target[2] = cz;
335     memcpy(camera->position, camera->target, sizeof(camera->position));
336     camera->position[0] = bmax[0] + .75 * size;
337     camera->position[1] = bmin[1] - .75 * size;
338     camera->position[2] = bmax[2] + .75 * size;
339     camera->near_range = ( camera->position[0] - bmax[0] ) * .5;
340     camera->far_range = ( camera->position[0] - bmin[0] ) * 3;
341     lib3ds_file_insert_camera(file, camera);
342   }
343 
344 
345   /* No lights in the file?  Add some. */
346 
347   if (file->lights == NULL)
348   {
349     Lib3dsLight *light;
350 
351     light = lib3ds_light_new("light0");
352     light->spot_light = 0;
353     light->see_cone = 0;
354     light->color[0] = light->color[1] = light->color[2] = .6;
355     light->position[0] = cx + size * .75;
356     light->position[1] = cy - size * 1.;
357     light->position[2] = cz + size * 1.5;
358     light->position[3] = 0.;
359     light->outer_range = 100;
360     light->inner_range = 10;
361     light->multiplier = 1;
362     lib3ds_file_insert_light(file, light);
363 
364     light = lib3ds_light_new("light1");
365     light->spot_light = 0;
366     light->see_cone = 0;
367     light->color[0] = light->color[1] = light->color[2] = .3;
368     light->position[0] = cx - size;
369     light->position[1] = cy - size;
370     light->position[2] = cz + size * .75;
371     light->position[3] = 0.;
372     light->outer_range = 100;
373     light->inner_range = 10;
374     light->multiplier = 1;
375     lib3ds_file_insert_light(file, light);
376 
377     light = lib3ds_light_new("light2");
378     light->spot_light = 0;
379     light->see_cone = 0;
380     light->color[0] = light->color[1] = light->color[2] = .3;
381     light->position[0] = cx;
382     light->position[1] = cy + size;
383     light->position[2] = cz + size;
384     light->position[3] = 0.;
385     light->outer_range = 100;
386     light->inner_range = 10;
387     light->multiplier = 1;
388     lib3ds_file_insert_light(file, light);
389 
390   }
391 
392   if (!file->cameras) {
393     fputs("3dsplayer: Error: No Camera found.\n", stderr);
394     lib3ds_file_free(file);
395     file=0;
396     exit(1);
397   }
398   if (!camera) {
399     camera=file->cameras->name;
400   }
401 
402   lib3ds_file_eval(file,0.);
403 }
404 
405 
406 
407 #ifdef  USE_SDL
408 /**
409 * Convert an SDL bitmap for use with OpenGL.
410 *
411 * Written by Gernot < gz@lysator.liu.se >
412 */
convert_to_RGB_Surface(SDL_Surface * bitmap)413 void *convert_to_RGB_Surface(SDL_Surface *bitmap)
414 {
415   unsigned char *pixel = (unsigned char *)malloc(sizeof(char) * 4 * bitmap->h * bitmap->w);
416   int soff = 0;
417   int doff = 0;
418   int x, y;
419   unsigned char *spixels = (unsigned char *)bitmap->pixels;
420   SDL_Palette *pal = bitmap->format->palette;
421 
422   for (y = 0; y < bitmap->h; y++)
423     for (x = 0; x < bitmap->w; x++)
424     {
425       SDL_Color* col = &pal->colors[spixels[soff]];
426 
427       pixel[doff] = col->r;
428       pixel[doff+1] = col->g;
429       pixel[doff+2] = col->b;
430       pixel[doff+3] = 255;
431       doff += 4;
432       soff++;
433     }
434 
435     return (void *)pixel;
436 }
437 #endif
438 
439 
440 
441 
442 /*!
443 * Render node recursively, first children, then parent.
444 * Each node receives its own OpenGL display list.
445 */
446 static void
render_node(Lib3dsNode * node)447 render_node(Lib3dsNode *node)
448 {
449   ASSERT(file);
450 
451   {
452     Lib3dsNode *p;
453     for (p=node->childs; p!=0; p=p->next) {
454       render_node(p);
455     }
456   }
457   if (node->type==LIB3DS_OBJECT_NODE) {
458     Lib3dsMesh *mesh;
459 
460     if (strcmp(node->name,"$$$DUMMY")==0) {
461       return;
462     }
463 
464     mesh = lib3ds_file_mesh_by_name(file, node->data.object.morph);
465     if( mesh == NULL )
466       mesh = lib3ds_file_mesh_by_name(file, node->name);
467 
468     if (!mesh->user.d) {
469       ASSERT(mesh);
470       if (!mesh) {
471         return;
472       }
473 
474       mesh->user.d=glGenLists(1);
475       glNewList(mesh->user.d, GL_COMPILE);
476 
477       {
478         unsigned p;
479         Lib3dsVector *normalL=malloc(3*sizeof(Lib3dsVector)*mesh->faces);
480         Lib3dsMaterial *oldmat = (Lib3dsMaterial *)-1;
481         {
482           Lib3dsMatrix M;
483           lib3ds_matrix_copy(M, mesh->matrix);
484           lib3ds_matrix_inv(M);
485           glMultMatrixf(&M[0][0]);
486         }
487         lib3ds_mesh_calculate_normals(mesh, normalL);
488 
489         for (p=0; p<mesh->faces; ++p) {
490           Lib3dsFace *f=&mesh->faceL[p];
491           Lib3dsMaterial *mat=0;
492 #ifdef	USE_SDL
493           Player_texture *pt = NULL;
494           int tex_mode = 0;
495 #endif
496           if (f->material[0]) {
497             mat=lib3ds_file_material_by_name(file, f->material);
498           }
499 
500           if( mat != oldmat ) {
501             if (mat) {
502               if( mat->two_sided )
503                 glDisable(GL_CULL_FACE);
504               else
505                 glEnable(GL_CULL_FACE);
506 
507               glDisable(GL_CULL_FACE);
508 
509               /* Texturing added by Gernot < gz@lysator.liu.se > */
510 
511               if (mat->texture1_map.name[0]) {		/* texture map? */
512                 Lib3dsTextureMap *tex = &mat->texture1_map;
513                 if (!tex->user.p) {		/* no player texture yet? */
514                   char texname[1024];
515                   pt = malloc(sizeof(*pt));
516                   tex->user.p = pt;
517                   //snprintf(texname, sizeof(texname), "%s/%s", datapath, tex->name);
518                   strcpy(texname, datapath);
519                   strcat(texname, "/");
520                   strcat(texname, tex->name);
521 #ifdef	DEBUG
522                   printf("Loading texture map, name %s\n", texname);
523 #endif	/* DEBUG */
524 #ifdef	USE_SDL
525 #ifdef  USE_SDL_IMG_load
526                   pt->bitmap = IMG_load(texname);
527 #else
528                   pt->bitmap = IMG_Load(texname);
529 #endif /* IMG_Load */
530 
531 #else /* USE_SDL */
532                   pt->bitmap = NULL;
533                   fputs("3dsplayer: Warning: No image loading support, skipping texture.\n", stderr);
534 #endif /* USE_SDL */
535                   if (pt->bitmap) {	/* could image be loaded ? */
536                     /* this OpenGL texupload code is incomplete format-wise!
537                     * to make it complete, examine SDL_surface->format and
538                     * tell us @lib3ds.sf.net about your improvements :-)
539                     */
540                     int upload_format = GL_RED; /* safe choice, shows errors */
541 #ifdef USE_SDL
542                     int bytespp = pt->bitmap->format->BytesPerPixel;
543                     void *pixel = NULL;
544                     glGenTextures(1, &pt->tex_id);
545 #ifdef	DEBUG
546                     printf("Uploading texture to OpenGL, id %d, at %d bytepp\n",
547                       pt->tex_id, bytespp);
548 #endif	/* DEBUG */
549                     if (pt->bitmap->format->palette) {
550                       pixel = convert_to_RGB_Surface(pt->bitmap);
551                       upload_format = GL_RGBA;
552                     }
553                     else {
554                       pixel = pt->bitmap->pixels;
555                       /* e.g. this could also be a color palette */
556                       if (bytespp == 1) upload_format = GL_LUMINANCE;
557                       else if (bytespp == 3) upload_format = GL_RGB;
558                       else if (bytespp == 4) upload_format = GL_RGBA;
559                     }
560                     glBindTexture(GL_TEXTURE_2D, pt->tex_id);
561                     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
562                       TEX_XSIZE, TEX_YSIZE, 0,
563                       GL_RGBA, GL_UNSIGNED_BYTE, NULL);
564                     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
565                     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
566                     glTexParameteri(GL_TEXTURE_2D,
567                       GL_TEXTURE_MAG_FILTER, GL_LINEAR);
568                     glTexParameteri(GL_TEXTURE_2D,
569                       GL_TEXTURE_MIN_FILTER, GL_LINEAR);
570                     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
571                     glTexSubImage2D(GL_TEXTURE_2D,
572                       0, 0, 0, pt->bitmap->w, pt->bitmap->h,
573                       upload_format, GL_UNSIGNED_BYTE, pixel);
574                     pt->scale_x = (float)pt->bitmap->w/(float)TEX_XSIZE;
575                     pt->scale_y = (float)pt->bitmap->h/(float)TEX_YSIZE;
576 #endif /* USE_SDL */
577                     pt->valid = 1;
578                   }
579                   else {
580                     fprintf(stderr,
581                       "Load of texture %s did not succeed "
582                       "(format not supported !)\n",
583                       texname);
584                     pt->valid = 0;
585                   }
586                 }
587                 else {
588                   pt = (Player_texture *)tex->user.p;
589                 }
590                 tex_mode = pt->valid;
591               }
592               else {
593                 tex_mode = 0;
594               }
595               glMaterialfv(GL_FRONT, GL_AMBIENT, mat->ambient);
596               glMaterialfv(GL_FRONT, GL_DIFFUSE, mat->diffuse);
597               glMaterialfv(GL_FRONT, GL_SPECULAR, mat->specular);
598               glMaterialf(GL_FRONT, GL_SHININESS, pow(2, 10.0*mat->shininess));
599             }
600             else {
601               static const Lib3dsRgba a={0.7, 0.7, 0.7, 1.0};
602               static const Lib3dsRgba d={0.7, 0.7, 0.7, 1.0};
603               static const Lib3dsRgba s={1.0, 1.0, 1.0, 1.0};
604               glMaterialfv(GL_FRONT, GL_AMBIENT, a);
605               glMaterialfv(GL_FRONT, GL_DIFFUSE, d);
606               glMaterialfv(GL_FRONT, GL_SPECULAR, s);
607               glMaterialf(GL_FRONT, GL_SHININESS, pow(2, 10.0*0.5));
608             }
609             oldmat = mat;
610           }
611 
612           else if (mat != NULL && mat->texture1_map.name[0]) {
613             Lib3dsTextureMap *tex = &mat->texture1_map;
614             if (tex != NULL && tex->user.p != NULL) {
615               pt = (Player_texture *)tex->user.p;
616               tex_mode = pt->valid;
617             }
618           }
619 
620 
621           {
622             int i;
623 
624             if (tex_mode) {
625               //printf("Binding texture %d\n", pt->tex_id);
626               glEnable(GL_TEXTURE_2D);
627               glBindTexture(GL_TEXTURE_2D, pt->tex_id);
628             }
629 
630             glBegin(GL_TRIANGLES);
631             glNormal3fv(f->normal);
632             for (i=0; i<3; ++i) {
633               glNormal3fv(normalL[3*p+i]);
634 
635               if (tex_mode) {
636                 glTexCoord2f(mesh->texelL[f->points[i]][1]*pt->scale_x,
637                   pt->scale_y - mesh->texelL[f->points[i]][0]*pt->scale_y);
638               }
639 
640               glVertex3fv(mesh->pointL[f->points[i]].pos);
641             }
642             glEnd();
643 
644             if (tex_mode)
645               glDisable(GL_TEXTURE_2D);
646           }
647         }
648 
649         free(normalL);
650       }
651 
652       glEndList();
653     }
654 
655     if (mesh->user.d) {
656       Lib3dsObjectData *d;
657 
658       glPushMatrix();
659       d=&node->data.object;
660       glMultMatrixf(&node->matrix[0][0]);
661       glTranslatef(-d->pivot[0], -d->pivot[1], -d->pivot[2]);
662       glCallList(mesh->user.d);
663       /* glutSolidSphere(50.0, 20,20); */
664       glPopMatrix();
665       if( flush )
666         glFlush();
667     }
668   }
669 }
670 
671 
672 
673 
674 /*!
675 * Update information about a light.  Try to find corresponding nodes
676 * if possible, and copy values from nodes into light struct.
677 */
678 
679 static void
light_update(Lib3dsLight * l)680 light_update(Lib3dsLight *l)
681 {
682   Lib3dsNode *ln, *sn;
683 
684   ln = lib3ds_file_node_by_name(file, l->name, LIB3DS_LIGHT_NODE);
685   sn = lib3ds_file_node_by_name(file, l->name, LIB3DS_SPOT_NODE);
686 
687   if( ln != NULL ) {
688     memcpy(l->color, ln->data.light.col, sizeof(Lib3dsRgb));
689     memcpy(l->position, ln->data.light.pos, sizeof(Lib3dsVector));
690   }
691 
692   if( sn != NULL )
693     memcpy(l->spot, sn->data.spot.pos, sizeof(Lib3dsVector));
694 }
695 
696 
697 
698 
699 static	void
draw_bounds(Lib3dsVector tgt)700 draw_bounds(Lib3dsVector tgt)
701 {
702   double cx,cy,cz;
703   double lx,ly,lz;
704 
705   lx = sx / 10.; ly = sy / 10.; lz = sz / 10.;
706   cx = tgt[0]; cy = tgt[1]; cz = tgt[2];
707 
708   glDisable(GL_LIGHTING);
709   glColor4fv(white);
710   glBegin(GL_LINES);
711   glVertex3f(bmin[0],bmin[1],bmin[2]);
712   glVertex3f(bmax[0],bmin[1],bmin[2]);
713   glVertex3f(bmin[0],bmax[1],bmin[2]);
714   glVertex3f(bmax[0],bmax[1],bmin[2]);
715   glVertex3f(bmin[0],bmin[1],bmax[2]);
716   glVertex3f(bmax[0],bmin[1],bmax[2]);
717   glVertex3f(bmin[0],bmax[1],bmax[2]);
718   glVertex3f(bmax[0],bmax[1],bmax[2]);
719 
720   glVertex3f(bmin[0],bmin[1],bmin[2]);
721   glVertex3f(bmin[0],bmax[1],bmin[2]);
722   glVertex3f(bmax[0],bmin[1],bmin[2]);
723   glVertex3f(bmax[0],bmax[1],bmin[2]);
724   glVertex3f(bmin[0],bmin[1],bmax[2]);
725   glVertex3f(bmin[0],bmax[1],bmax[2]);
726   glVertex3f(bmax[0],bmin[1],bmax[2]);
727   glVertex3f(bmax[0],bmax[1],bmax[2]);
728 
729   glVertex3f(bmin[0],bmin[1],bmin[2]);
730   glVertex3f(bmin[0],bmin[1],bmax[2]);
731   glVertex3f(bmax[0],bmin[1],bmin[2]);
732   glVertex3f(bmax[0],bmin[1],bmax[2]);
733   glVertex3f(bmin[0],bmax[1],bmin[2]);
734   glVertex3f(bmin[0],bmax[1],bmax[2]);
735   glVertex3f(bmax[0],bmax[1],bmin[2]);
736   glVertex3f(bmax[0],bmax[1],bmax[2]);
737 
738   glVertex3f(cx-size/32, cy, cz);
739   glVertex3f(cx+size/32, cy, cz);
740   glVertex3f(cx, cy-size/32, cz);
741   glVertex3f(cx, cy+size/32, cz);
742   glVertex3f(cx, cy, cz-size/32);
743   glVertex3f(cx, cy, cz+size/32);
744   glEnd();
745 
746 
747   glColor4fv(red);
748   glBegin(GL_LINES);
749   glVertex3f(0.,0.,0.);
750   glVertex3f(lx,0.,0.);
751   glEnd();
752 
753   glColor4fv(green);
754   glBegin(GL_LINES);
755   glVertex3f(0.,0.,0.);
756   glVertex3f(0.,ly,0.);
757   glEnd();
758 
759   glColor4fv(blue);
760   glBegin(GL_LINES);
761   glVertex3f(0.,0.,0.);
762   glVertex3f(0.,0.,lz);
763   glEnd();
764 
765   glEnable(GL_LIGHTING);
766 }
767 
768 
769 static void
draw_light(const GLfloat * pos,const GLfloat * color)770 draw_light(const GLfloat *pos, const GLfloat *color)
771 {
772   glMaterialfv(GL_FRONT, GL_EMISSION, color);
773   glPushMatrix();
774   glTranslatef(pos[0], pos[1], pos[2]);
775   glScalef(size/20, size/20, size/20);
776   glCallList(lightList);
777   glPopMatrix();
778 }
779 
780 
781 
782 /*!
783 * Main display function; called whenever the scene needs to be redrawn.
784 */
785 static void
display(void)786 display(void)
787 {
788   Lib3dsNode *c,*t;
789   Lib3dsFloat fov, roll;
790   float near, far, dist;
791   float *campos;
792   float *tgt;
793   Lib3dsMatrix M;
794   Lib3dsCamera *cam;
795   Lib3dsVector v;
796   Lib3dsNode *p;
797 
798   if( file != NULL && file->background.solid.use )
799     glClearColor(file->background.solid.col[0],
800     file->background.solid.col[1],
801     file->background.solid.col[2], 1.);
802 
803   /* TODO: fog */
804 
805   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
806 
807   if( anti_alias )
808     glEnable(GL_POLYGON_SMOOTH);
809   else
810     glDisable(GL_POLYGON_SMOOTH);
811 
812 
813   if (!file) {
814     return;
815   }
816 
817   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, file->ambient);
818 
819   c=lib3ds_file_node_by_name(file, camera, LIB3DS_CAMERA_NODE);
820   t=lib3ds_file_node_by_name(file, camera, LIB3DS_TARGET_NODE);
821 
822   if( t != NULL )
823     tgt = t->data.target.pos;
824 
825   if( c != NULL ) {
826     fov = c->data.camera.fov;
827     roll = c->data.camera.roll;
828     campos = c->data.camera.pos;
829   }
830 
831   if ((cam = lib3ds_file_camera_by_name(file, camera)) == NULL)
832     return;
833 
834   near = cam->near_range;
835   far = cam->far_range;
836 
837   if (c == NULL || t == NULL) {
838     if( c == NULL ) {
839       fov = cam->fov;
840       roll = cam->roll;
841       campos = cam->position;
842     }
843     if( t == NULL )
844       tgt = cam->target;
845   }
846 
847   glMatrixMode(GL_PROJECTION);
848   glLoadIdentity();
849 
850   /* KLUDGE alert:  OpenGL can't handle a near clip plane of zero,
851   * so if the camera's near plane is zero, we give it a small number.
852   * In addition, many .3ds files I've seen have a far plane that's
853   * much too close and the model gets clipped away.  I haven't found
854   * a way to tell OpenGL not to clip the far plane, so we move it
855   * further away.  A factor of 10 seems to make all the models I've
856   * seen visible.
857   */
858   if( near <= 0. ) near = far * .001;
859 
860   gluPerspective( fov, 1.0*gl_width/gl_height, near, far);
861 
862   glMatrixMode(GL_MODELVIEW);
863   glLoadIdentity();
864   glRotatef(-90, 1.0,0,0);
865 
866   /* User rotates the view about the target point */
867 
868   lib3ds_vector_sub(v, tgt, campos);
869   dist = lib3ds_vector_length(v);
870 
871   glTranslatef(0.,dist, 0.);
872   glRotatef(view_rotx, 1., 0., 0.);
873   glRotatef(view_roty, 0., 1., 0.);
874   glRotatef(view_rotz, 0., 0., 1.);
875   glTranslatef(0.,-dist, 0.);
876 
877   lib3ds_matrix_camera(M, campos, tgt, roll);
878   glMultMatrixf(&M[0][0]);
879 
880   /* Lights.  Set them from light nodes if possible.  If not, use the
881   * light objects directly.
882   */
883   {
884     static const GLfloat a[] = {0.0f, 0.0f, 0.0f, 1.0f};
885     static GLfloat c[] = {1.0f, 1.0f, 1.0f, 1.0f};
886     static GLfloat p[] = {0.0f, 0.0f, 0.0f, 1.0f};
887     Lib3dsLight *l;
888 
889     int li=GL_LIGHT0;
890     for (l=file->lights; l; l=l->next) {
891       glEnable(li);
892 
893       light_update(l);
894 
895       c[0] = l->color[0];
896       c[1] = l->color[1];
897       c[2] = l->color[2];
898       glLightfv(li, GL_AMBIENT, a);
899       glLightfv(li, GL_DIFFUSE, c);
900       glLightfv(li, GL_SPECULAR, c);
901 
902       p[0] = l->position[0];
903       p[1] = l->position[1];
904       p[2] = l->position[2];
905       glLightfv(li, GL_POSITION, p);
906 
907       if (l->spot_light) {
908         p[0] = l->spot[0] - l->position[0];
909         p[1] = l->spot[1] - l->position[1];
910         p[2] = l->spot[2] - l->position[2];
911         glLightfv(li, GL_SPOT_DIRECTION, p);
912       }
913       ++li;
914     }
915   }
916 
917 
918 
919 
920   if( show_object )
921   {
922     for (p=file->nodes; p!=0; p=p->next) {
923       render_node(p);
924     }
925   }
926 
927   if( show_bounds )
928     draw_bounds(tgt);
929 
930   if( show_cameras )
931   {
932     for( cam = file->cameras; cam != NULL; cam = cam->next )
933     {
934       lib3ds_matrix_camera(M, cam->position, cam->target, cam->roll);
935       lib3ds_matrix_inv(M);
936 
937       glPushMatrix();
938       glMultMatrixf(&M[0][0]);
939       glScalef(size/20, size/20, size/20);
940       glCallList(cameraList);
941       glPopMatrix();
942     }
943   }
944 
945   if( show_lights )
946   {
947     Lib3dsLight *light;
948     for( light = file->lights; light != NULL; light = light->next )
949       draw_light(light->position, light->color);
950     glMaterialfv(GL_FRONT, GL_EMISSION, black);
951   }
952 
953 
954   glutSwapBuffers();
955 }
956 
957 
958 /*!
959 *
960 */
961 static void
reshape(int w,int h)962 reshape (int w, int h)
963 {
964   gl_width=w;
965   gl_height=h;
966   glViewport(0,0,w,h);
967 }
968 
969 
970 /*!
971 *
972 */
973 static void
keyboard(unsigned char key,int x,int y)974 keyboard(unsigned char key, int x, int y)
975 {
976   switch (key) {
977 case 27:
978   exit(0);
979   break;
980 case 'h':
981   set_halt(!halt);
982   break;
983 case 'a':
984   anim_rotz += .05;
985   break;
986 case 'A':
987   anim_rotz -= .05;
988   break;
989 case 'r':
990   view_rotx = view_roty = view_rotz = anim_rotz = 0.;
991   break;
992 case 'z':
993   view_roty += 5.;
994   break;
995 case 'Z':
996   view_roty -= 5.;
997   break;
998 case 'b':
999   show_bounds = !show_bounds;
1000   break;
1001 case 'o':
1002   show_object = !show_object;
1003   break;
1004 case '\001':
1005   anti_alias = !anti_alias;
1006   break;
1007   }
1008 }
1009 
1010 
1011 /*!
1012 * Respond to mouse buttons.  Action depends on current operating mode.
1013 */
1014 static	void
mouse_cb(int button,int state,int x,int y)1015 mouse_cb(int button, int state, int x, int y)
1016 {
1017   mx = x; my = y;
1018   switch( button ) {
1019 case GLUT_LEFT_BUTTON:
1020   switch( runMode ) {
1021 case ROTATING: rotating = state == GLUT_DOWN; break;
1022 default: break;
1023   }
1024   break;
1025 default:
1026   break;
1027   }
1028 }
1029 
1030 
1031 /*!
1032 * Respond to mouse motions; left button rotates the image or performs
1033 * other action according to current operating mode.
1034 */
1035 static void
drag_cb(int x,int y)1036 drag_cb(int x, int y)
1037 {
1038   if( rotating ) {
1039     view_rotz += MOUSE_SCALE * (x - mx);
1040     view_rotx += MOUSE_SCALE * (y - my);
1041     mx = x;
1042     my = y;
1043     glutPostRedisplay();
1044   }
1045 }
1046 
1047 
1048 /*!
1049 * Create camera and light icons
1050 */
1051 static void
create_icons()1052 create_icons()
1053 {
1054   GLUquadricObj *qobj;
1055 
1056 #define	CBX	.25	// camera body dimensions
1057 #define	CBY	1.5
1058 #define	CBZ	1.
1059 
1060   qobj = gluNewQuadric();
1061   gluQuadricDrawStyle(qobj, GLU_FILL);
1062   gluQuadricNormals(qobj, GLU_SMOOTH);
1063 
1064   cameraList = glGenLists(1);
1065   glNewList(cameraList, GL_COMPILE);
1066   glMaterialfv(GL_FRONT, GL_AMBIENT, dgrey);
1067   glMaterialfv(GL_FRONT, GL_DIFFUSE, lgrey);
1068   glMaterialfv(GL_FRONT, GL_SPECULAR, black);
1069   glEnable(GL_CULL_FACE);
1070   solidBox(CBX,CBY,CBZ);
1071   glPushMatrix();
1072   glTranslatef(0.,.9,1.8);
1073   glRotatef(90., 0.,1.,0.);
1074   solidCylinder(1., CBX*2, 12);
1075   glTranslatef(0.,-1.8,0.);
1076   solidCylinder(1., CBX*2, 12);
1077   glPopMatrix();
1078   glDisable(GL_CULL_FACE);
1079   glPushMatrix();
1080   glTranslated(0.,CBY,0.);
1081   glRotated(-90., 1.,0.,0.);
1082   gluCylinder(qobj, .2, .5, 1., 12, 1);
1083   glPopMatrix();
1084   glEndList();
1085 
1086   lightList = glGenLists(1);
1087   glNewList(lightList, GL_COMPILE);
1088   glPushMatrix();
1089   glMaterialfv(GL_FRONT, GL_AMBIENT, dgrey);
1090   glMaterialfv(GL_FRONT, GL_DIFFUSE, dgrey);
1091   glMaterialfv(GL_FRONT, GL_SPECULAR, grey);
1092   glEnable(GL_CULL_FACE);
1093   gluSphere(qobj, .5, 12., 6.);
1094   glRotated(180.,1.,0.,0.);
1095   glMaterialfv(GL_FRONT, GL_EMISSION, dgrey);
1096   gluCylinder(qobj, .2, .2, 1., 12, 1);
1097   glPopMatrix();
1098   glEndList();
1099 }
1100 
1101 
decompose_datapath(const char * fn)1102 void decompose_datapath(const char *fn)
1103 {
1104   const char *ptr = strrchr(fn, '/');
1105 
1106   if (ptr == NULL) {
1107     strcpy(datapath, ".");
1108     strcpy(filename, fn);
1109   }
1110   else {
1111     strcpy(filename, ptr+1);
1112     strcpy(datapath, fn);
1113     datapath[ptr - fn] = '\0';
1114   }
1115 }
1116 
1117 
1118 /*!
1119 *
1120 */
1121 int
main(int argc,char ** argv)1122 main(int argc, char** argv)
1123 {
1124   char *progname = argv[0];
1125 
1126   glutInit(&argc, argv);
1127 
1128   for(++argv; --argc > 0; ++argv)
1129   {
1130     if( strcmp(*argv, "-help") ==  0 || strcmp(*argv, "--help") == 0 )
1131     {
1132       fputs("View a 3DS model file using OpenGL.\n", stderr);
1133       fputs("Usage: 3dsplayer [-nodb|-aa|-flush] <filename>\n", stderr);
1134 #ifndef USE_SDL
1135       fputs("Texture rendering is not available; install SDL_image and recompile.\n", stderr);
1136 #endif
1137       exit(0);
1138     }
1139     else if( strcmp(*argv, "-nodb") == 0 )
1140       dbuf = 0;
1141     else if( strcmp(*argv, "-aa") == 0 )
1142       anti_alias = 1;
1143     else if( strcmp(*argv, "-flush") == 0 )
1144       flush = 1;
1145     else {
1146       filepath = *argv;
1147       decompose_datapath(filepath);
1148     }
1149   }
1150 
1151   if (filepath == NULL) {
1152     fputs("3dsplayer: Error: No 3DS file specified\n", stderr);
1153     exit(1);
1154   }
1155 
1156   glutInitDisplayMode(GLUT_DEPTH | GLUT_RGB | (dbuf ? GLUT_DOUBLE:0) );
1157   glutInitWindowSize(500, 500);
1158   glutInitWindowPosition(100, 100);
1159   glutCreateWindow(filepath != NULL ? Basename(filepath) : progname);
1160 
1161   init();
1162   create_icons();
1163   load_model();
1164 
1165   build_menu();
1166   glutAttachMenu(2);
1167 
1168   glutDisplayFunc(display);
1169   glutReshapeFunc(reshape);
1170   glutKeyboardFunc(keyboard);
1171   glutMouseFunc(mouse_cb);
1172   glutMotionFunc(drag_cb);
1173   glutTimerFunc(10, timer_cb, 0);
1174   glutMainLoop();
1175   return(0);
1176 }
1177 
1178 
1179 
1180 
1181 
1182 /* A few small utilities, so generic that they probably
1183 * don't even belong in this file.
1184 */
1185 
1186 
1187 
1188 /*!
1189 * Render a box, centered at 0,0,0
1190 *
1191 * Box may be rendered with face culling enabled.
1192 */
1193 static void
solidBox(double bx,double by,double bz)1194 solidBox(double bx, double by, double bz)
1195 {
1196   glBegin(GL_POLYGON);
1197   glNormal3d(0.,0.,1.);
1198   glVertex3d(bx,by,bz);
1199   glVertex3d(-bx,by,bz);
1200   glVertex3d(-bx,-by,bz);
1201   glVertex3d(bx,-by,bz);
1202   glEnd();
1203   glBegin(GL_POLYGON);
1204   glNormal3d(0.,0.,-1.);
1205   glVertex3d(-bx,by,-bz);
1206   glVertex3d(bx,by,-bz);
1207   glVertex3d(bx,-by,-bz);
1208   glVertex3d(-bx,-by,-bz);
1209   glEnd();
1210   glBegin(GL_POLYGON);
1211   glNormal3d(0.,-1.,0.);
1212   glVertex3d(-bx,by,bz);
1213   glVertex3d(bx,by,bz);
1214   glVertex3d(bx,by,-bz);
1215   glVertex3d(-bx,by,-bz);
1216   glEnd();
1217   glBegin(GL_POLYGON);
1218   glNormal3d(0.,-1.,0.);
1219   glVertex3d(bx,-by,bz);
1220   glVertex3d(-bx,-by,bz);
1221   glVertex3d(-bx,-by,-bz);
1222   glVertex3d(bx,-by,-bz);
1223   glEnd();
1224   glBegin(GL_POLYGON);
1225   glNormal3d(1.,0.,0.);
1226   glVertex3d(bx,by,bz);
1227   glVertex3d(bx,-by,bz);
1228   glVertex3d(bx,-by,-bz);
1229   glVertex3d(bx,by,-bz);
1230   glEnd();
1231   glBegin(GL_POLYGON);
1232   glNormal3d(-1.,0.,0.);
1233   glVertex3d(-bx,by,-bz);
1234   glVertex3d(-bx,-by,-bz);
1235   glVertex3d(-bx,-by,bz);
1236   glVertex3d(-bx,by,bz);
1237   glEnd();
1238 }
1239 
1240 
1241 
1242 /*!
1243 * Render a cylinder with end caps, along the Z axis centered at 0,0,0
1244 *
1245 * Cylinder may be rendered with face culling enabled.
1246 */
1247 static void
solidCylinder(double r,double h,int slices)1248 solidCylinder(double r, double h, int slices)
1249 {
1250   GLUquadricObj *qobj = gluNewQuadric();
1251   gluQuadricDrawStyle(qobj, GLU_FILL);
1252   gluQuadricNormals(qobj, GLU_SMOOTH);
1253 
1254   glPushMatrix();
1255   glTranslated(0., 0., -h/2);
1256   gluCylinder( qobj, r, r, h, slices, 1 );
1257   glPushMatrix();
1258   glRotated(180., 1.,0.,0.);
1259   gluDisk( qobj, 0., r, slices, 1 );
1260   glPopMatrix();
1261   glTranslated(0., 0., h);
1262   gluDisk( qobj, 0., r, slices, 1 );
1263   glPopMatrix();
1264 }
1265 
1266 
1267 static const char *
Basename(const char * filename)1268 Basename(const char *filename)
1269 {
1270   char *ptr = strrchr(filename, '/');
1271   return ptr != NULL ? ptr+1 : filename;
1272 }
1273 
1274 
1275 
1276 /*
1277 * This module is a crude front end to the GLUT menu system, allowing for
1278 * slightly more sophisticated callbacks.
1279 */
1280 
1281 #include <stdio.h>
1282 
1283 #define	MAX_CALLBACKS	100
1284 
1285 typedef struct {
1286   void (*cb)(int, int, void *);
1287   void	*client;
1288 } Callback;
1289 
1290 
1291 static	Callback	callbacks[MAX_CALLBACKS];
1292 static	int		ncb = 0;
1293 
1294 /*!
1295 * Register a callback, returning an integer value suitable for
1296 * passing to glutAddMenuEntry()
1297 *
1298 * \param cb Callback function to be called.
1299 * \param client Data to be passed to the callback.
1300 *
1301 * \return integer callback id
1302 */
1303 static int
callback(void (* cb)(int,int,void * client),void * client)1304 callback(void (*cb)(int, int, void *client), void *client)
1305 {
1306   if( ncb == 0 )
1307   {
1308     int i;
1309     for(i=0; i < NA(callbacks); ++i)
1310       callbacks[i].cb = NULL;
1311   }
1312   else if( ncb >= NA(callbacks) ) {
1313     fprintf(stderr,
1314       "callback() out of callbacks, try changing MAX_CALLBACKS\n");
1315   }
1316 
1317   callbacks[ncb].cb = cb;
1318   callbacks[ncb].client = client;
1319   return ncb++;
1320 }
1321 
1322 
1323 /*!
1324 * Call the indexed callback.
1325 *
1326 * \param idx Callback index.
1327 * \param data Data to be passed to the callback
1328 */
1329 static void
call_callback(int idx,int data)1330 call_callback(int idx, int data)
1331 {
1332   if( idx >= 0 && idx < NA(callbacks) && callbacks[idx].cb != NULL )
1333     callbacks[idx].cb(idx, data, callbacks[idx].client);
1334 }
1335