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