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