/* Copyright (C) 1996-2008 by Jan Eric Kyprianidis <www.kyprianidis.com> All rights reserved. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) any later version. Thisprogram is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; If not, see <http://www.gnu.org/licenses/>. */ #include <lib3ds.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <math.h> #include <assert.h> #define FRAMES_PER_SECOND 10 // OS X has a different path than everyone else #ifdef __APPLE__ #include <GLUT/glut.h> #else #include <GL/glut.h> #endif #ifdef USE_SDL #include <SDL_image.h> #endif #define MOUSE_SCALE .1 /* degrees/pixel movement */ /* Previews a <i>3DS</i> file using OpenGL. \warning To compile this program you must have OpenGL and glut installed. */ typedef enum {ROTATING, WALKING} RunMode; static RunMode runMode = ROTATING; static const char* filepath = NULL; static char datapath[256]; static char filename[256]; static int dbuf = 1; static int halt = 0; static int flush = 0; static int anti_alias = 1; static const char* camera = 0; static Lib3dsFile *file = 0; static float current_frame = 0.0; static int gl_width; static int gl_height; static int menu_id = 0; static int show_object = 1; static int show_bounds = 0; static int rotating = 0; static int show_cameras = 0; static int show_lights = 0; static int cameraList, lightList; /* Icon display lists */ static float bmin[3], bmax[3]; static float sx, sy, sz, size; /* bounding box dimensions */ static float cx, cy, cz; /* bounding box center */ static float view_rotx = 0., view_roty = 0., view_rotz = 0.; static float anim_rotz = 0.; static int mx, my; static const GLfloat white[4] = {1., 1., 1., 1.}; static const GLfloat dgrey[4] = {.25, .25, .25, 1.}; static const GLfloat grey[4] = {.5, .5, .5, 1.}; static const GLfloat lgrey[4] = {.75, .75, .75, 1.}; static const GLfloat black[] = {0., 0., 0., 1.}; static const GLfloat red[4] = {1., 0., 0., 1.}; static const GLfloat green[4] = {0., 1., 0., 1.}; static const GLfloat blue[4] = {0., 0., 1., 1.}; static void solidBox(double bx, double by, double bz); static void solidCylinder(double r, double h, int slices); static int callback(void (*cb)(int m, int d, void *), void *client); static void call_callback(int idx, int data); static void solidBox(double bx, double by, double bz); static void solidCylinder(double r, double h, int slices); static const char *Basename(const char *filename); // texture size: by now minimum standard #define TEX_XSIZE 1024 #define TEX_YSIZE 1024 struct _player_texture { int valid; // was the loading attempt successful ? #ifdef USE_SDL SDL_Surface *bitmap; #else void *bitmap; #endif GLuint tex_id; //OpenGL texture ID float scale_x, scale_y; // scale the texcoords, as OpenGL thinks in TEX_XSIZE and TEX_YSIZE }; typedef struct _player_texture Player_texture; Player_texture *pt; int tex_mode; // Texturing active ? #define NA(a) (sizeof(a)/sizeof(a[0])) #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b)) #endif static void menu_cb(int value) { call_callback(value, 0); } /*! * Switch cameras based on user's menu choice. */ static void camera_menu(int menu, int value, void *client) { Lib3dsCamera *c = (Lib3dsCamera*)client; view_rotx = view_roty = view_rotz = anim_rotz = 0.; camera = c->name; } /*! * Toggle an arbitrary int (bool) variable */ static void toggle_bool(int menu, int value, void *client) { int *var = (int*)client; *var = !*var; glutPostRedisplay(); } /*! * Build the menu */ static void build_menu() { int i; menu_id = glutCreateMenu(menu_cb); for (i = 0; i < file->ncameras; ++i) glutAddMenuEntry(file->cameras[i]->name, callback(camera_menu, file->cameras[i])); glutAddMenuEntry("Show cameras", callback(toggle_bool, &show_cameras)); glutAddMenuEntry("Show lights", callback(toggle_bool, &show_lights)); glutAddMenuEntry("Show bounds", callback(toggle_bool, &show_bounds)); } #define LIB3DS_PI 3.14159265358979323846 /*! * Time function, called every frame */ static void timer_cb(int value) { if (!halt) { view_rotz += anim_rotz; current_frame+= 1; if (current_frame > file->frames) current_frame = 0; lib3ds_file_eval(file, current_frame); glutTimerFunc(1000 / FRAMES_PER_SECOND, timer_cb, 0); } glutPostRedisplay(); } static void set_halt(int h) { if (h != halt) { halt = h; if (!halt) glutTimerFunc(10, timer_cb, 0); } } /*! * Initialize OpenGL */ static void init(void) { glClearColor(0.5, 0.5, 0.5, 1.0); glShadeModel(GL_SMOOTH); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glDisable(GL_LIGHT1); glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); //glDisable(GL_NORMALIZE); //glPolygonOffset(1.0, 2); } /*! * Load the model from .3ds file. */ static void load_model(void) { file = lib3ds_file_open(filepath); if (!file) { puts("3dsplayer: Error: Loading 3DS file failed.\n"); exit(1); } /* No nodes? Fabricate nodes to display all the meshes. */ if (!file->nodes) { Lib3dsNode *node; int i; for (i = 0; i < file->nmeshes; ++i) { Lib3dsMesh *mesh = file->meshes[i]; node = lib3ds_node_new(LIB3DS_NODE_MESH_INSTANCE); strcpy(node->name, mesh->name); lib3ds_file_insert_node(file, node, NULL); } } lib3ds_file_eval(file, 0.0f); lib3ds_file_bounding_box_of_nodes(file, 1, 0, 0, bmin, bmax, NULL); sx = bmax[0] - bmin[0]; sy = bmax[1] - bmin[1]; sz = bmax[2] - bmin[2]; size = MAX(sx, sy); size = MAX(size, sz); cx = (bmin[0] + bmax[0]) / 2; cy = (bmin[1] + bmax[1]) / 2; cz = (bmin[2] + bmax[2]) / 2; /* No cameras in the file? Add four */ if (!file->ncameras) { /* Add some cameras that encompass the bounding box */ Lib3dsCamera *camera = lib3ds_camera_new("Camera_X"); camera->target[0] = cx; camera->target[1] = cy; camera->target[2] = cz; memcpy(camera->position, camera->target, sizeof(camera->position)); camera->position[0] = bmax[0] + 1.5 * MAX(sy, sz); camera->near_range = (camera->position[0] - bmax[0]) * .5; camera->far_range = (camera->position[0] - bmin[0]) * 2; lib3ds_file_insert_camera(file, camera, -1); /* Since lib3ds considers +Y to be into the screen, we'll put * this camera on the -Y axis, looking in the +Y direction. */ camera = lib3ds_camera_new("Camera_Y"); camera->target[0] = cx; camera->target[1] = cy; camera->target[2] = cz; memcpy(camera->position, camera->target, sizeof(camera->position)); camera->position[1] = bmin[1] - 1.5 * MAX(sx, sz); camera->near_range = (bmin[1] - camera->position[1]) * .5; camera->far_range = (bmax[1] - camera->position[1]) * 2; lib3ds_file_insert_camera(file, camera, -1); camera = lib3ds_camera_new("Camera_Z"); camera->target[0] = cx; camera->target[1] = cy; camera->target[2] = cz; memcpy(camera->position, camera->target, sizeof(camera->position)); camera->position[2] = bmax[2] + 1.5 * MAX(sx, sy); camera->near_range = (camera->position[2] - bmax[2]) * .5; camera->far_range = (camera->position[2] - bmin[2]) * 2; lib3ds_file_insert_camera(file, camera, -1); camera = lib3ds_camera_new("Camera_ISO"); camera->target[0] = cx; camera->target[1] = cy; camera->target[2] = cz; memcpy(camera->position, camera->target, sizeof(camera->position)); camera->position[0] = bmax[0] + .75 * size; camera->position[1] = bmin[1] - .75 * size; camera->position[2] = bmax[2] + .75 * size; camera->near_range = (camera->position[0] - bmax[0]) * .5; camera->far_range = (camera->position[0] - bmin[0]) * 3; lib3ds_file_insert_camera(file, camera, -1); } /* No lights in the file? Add some. */ if (!file->nlights) { Lib3dsLight *light; light = lib3ds_light_new("light0"); light->spot_light = 0; light->see_cone = 0; light->color[0] = light->color[1] = light->color[2] = .6; light->position[0] = cx + size * .75; light->position[1] = cy - size * 1.; light->position[2] = cz + size * 1.5; light->position[3] = 0.; light->outer_range = 100; light->inner_range = 10; light->multiplier = 1; lib3ds_file_insert_light(file, light, -1); light = lib3ds_light_new("light1"); light->spot_light = 0; light->see_cone = 0; light->color[0] = light->color[1] = light->color[2] = .3; light->position[0] = cx - size; light->position[1] = cy - size; light->position[2] = cz + size * .75; light->position[3] = 0.; light->outer_range = 100; light->inner_range = 10; light->multiplier = 1; lib3ds_file_insert_light(file, light, -1); light = lib3ds_light_new("light2"); light->spot_light = 0; light->see_cone = 0; light->color[0] = light->color[1] = light->color[2] = .3; light->position[0] = cx; light->position[1] = cy + size; light->position[2] = cz + size; light->position[3] = 0.; light->outer_range = 100; light->inner_range = 10; light->multiplier = 1; lib3ds_file_insert_light(file, light, -1); } camera = file->cameras[0]->name; lib3ds_file_eval(file, 0); } #ifdef USE_SDL /** * Convert an SDL bitmap for use with OpenGL. * * Written by Gernot < gz@lysator.liu.se > */ void *convert_to_RGB_Surface(SDL_Surface *bitmap) { unsigned char *pixel = (unsigned char *)malloc(sizeof(char) * 4 * bitmap->h * bitmap->w); int soff = 0; int doff = 0; int x, y; unsigned char *spixels = (unsigned char *)bitmap->pixels; SDL_Palette *pal = bitmap->format->palette; for (y = 0; y < bitmap->h; y++) for (x = 0; x < bitmap->w; x++) { SDL_Color* col = &pal->colors[spixels[soff]]; pixel[doff] = col->r; pixel[doff+1] = col->g; pixel[doff+2] = col->b; pixel[doff+3] = 255; doff += 4; soff++; } return (void *)pixel; } #endif /*! * Render node recursively, first children, then parent. * Each node receives its own OpenGL display list. */ static void render_node(Lib3dsNode *node) { assert(file); { Lib3dsNode *p; for (p = node->childs; p != 0; p = p->next) { render_node(p); } } if (node->type == LIB3DS_NODE_MESH_INSTANCE) { int index; Lib3dsMesh *mesh; Lib3dsMeshInstanceNode *n = (Lib3dsMeshInstanceNode*)node; if (strcmp(node->name, "$$$DUMMY") == 0) { return; } index = lib3ds_file_mesh_by_name(file, n->instance_name); if (index < 0) index = lib3ds_file_mesh_by_name(file, node->name); if (index < 0) { return; } mesh = file->meshes[index]; if (!mesh->user_id) { assert(mesh); mesh->user_id = glGenLists(1); glNewList(mesh->user_id, GL_COMPILE); { int p; float (*normalL)[3] = (float(*)[3])malloc(3 * 3 * sizeof(float) * mesh->nfaces); Lib3dsMaterial *oldmat = (Lib3dsMaterial *) - 1; { float M[4][4]; lib3ds_matrix_copy(M, mesh->matrix); lib3ds_matrix_inv(M); glMultMatrixf(&M[0][0]); } lib3ds_mesh_calculate_vertex_normals(mesh, normalL); for (p = 0; p < mesh->nfaces; ++p) { Lib3dsMaterial *mat = 0; #ifdef USE_SDL Player_texture *pt = NULL; int tex_mode = 0; #endif if (mesh->faces[p].material > 0) { mat = file->materials[mesh->faces[p].material]; } if (mat != oldmat) { if (mat) { if (mat->two_sided) glDisable(GL_CULL_FACE); else glEnable(GL_CULL_FACE); glDisable(GL_CULL_FACE); /* Texturing added by Gernot < gz@lysator.liu.se > */ if (mat->texture1_map.name[0]) { /* texture map? */ Lib3dsTextureMap *tex = &mat->texture1_map; if (!tex->user_ptr) { /* no player texture yet? */ char texname[1024]; pt = (Player_texture*)malloc(sizeof(*pt)); tex->user_ptr = pt; //snprintf(texname, sizeof(texname), "%s/%s", datapath, tex->name); strcpy(texname, datapath); strcat(texname, "/"); strcat(texname, tex->name); #ifdef DEBUG printf("Loading texture map, name %s\n", texname); #endif /* DEBUG */ #ifdef USE_SDL #ifdef USE_SDL_IMG_load pt->bitmap = IMG_load(texname); #else pt->bitmap = IMG_Load(texname); #endif /* IMG_Load */ #else /* USE_SDL */ pt->bitmap = NULL; fputs("3dsplayer: Warning: No image loading support, skipping texture.\n", stderr); #endif /* USE_SDL */ if (pt->bitmap) { /* could image be loaded ? */ /* this OpenGL texupload code is incomplete format-wise! * to make it complete, examine SDL_surface->format and * tell us @lib3ds.sf.net about your improvements :-) */ int upload_format = GL_RED; /* safe choice, shows errors */ #ifdef USE_SDL int bytespp = pt->bitmap->format->BytesPerPixel; void *pixel = NULL; glGenTextures(1, &pt->tex_id); #ifdef DEBUG printf("Uploading texture to OpenGL, id %d, at %d bytepp\n", pt->tex_id, bytespp); #endif /* DEBUG */ if (pt->bitmap->format->palette) { pixel = convert_to_RGB_Surface(pt->bitmap); upload_format = GL_RGBA; } else { pixel = pt->bitmap->pixels; /* e.g. this could also be a color palette */ if (bytespp == 1) upload_format = GL_LUMINANCE; else if (bytespp == 3) upload_format = GL_RGB; else if (bytespp == 4) upload_format = GL_RGBA; } glBindTexture(GL_TEXTURE_2D, pt->tex_id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEX_XSIZE, TEX_YSIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pt->bitmap->w, pt->bitmap->h, upload_format, GL_UNSIGNED_BYTE, pixel); pt->scale_x = (float)pt->bitmap->w / (float)TEX_XSIZE; pt->scale_y = (float)pt->bitmap->h / (float)TEX_YSIZE; #endif /* USE_SDL */ pt->valid = 1; } else { fprintf(stderr, "Load of texture %s did not succeed " "(format not supported !)\n", texname); pt->valid = 0; } } else { pt = (Player_texture *)tex->user_ptr; } tex_mode = pt->valid; } else { tex_mode = 0; } { float a[4], d[4], s[4]; int i; for (i=0; i<3; ++i) { a[i] = mat->ambient[i]; d[i] = mat->diffuse[i]; s[i] = mat->specular[i]; } a[3] = d[3] = s[3] = 1.0f; glMaterialfv(GL_FRONT, GL_AMBIENT, a); glMaterialfv(GL_FRONT, GL_DIFFUSE, d); glMaterialfv(GL_FRONT, GL_SPECULAR, s); } float shininess = pow(2, 10.0*mat->shininess); glMaterialf(GL_FRONT, GL_SHININESS, shininess <= 128? shininess : 128); } else { static const float a[4] = {0.7, 0.7, 0.7, 1.0}; static const float d[4] = {0.7, 0.7, 0.7, 1.0}; static const float s[4] = {1.0, 1.0, 1.0, 1.0}; glMaterialfv(GL_FRONT, GL_AMBIENT, a); glMaterialfv(GL_FRONT, GL_DIFFUSE, d); glMaterialfv(GL_FRONT, GL_SPECULAR, s); glMaterialf(GL_FRONT, GL_SHININESS, pow(2, 10.0*0.5)); } oldmat = mat; } else if (mat != NULL && mat->texture1_map.name[0]) { Lib3dsTextureMap *tex = &mat->texture1_map; if (tex != NULL && tex->user_ptr != NULL) { pt = (Player_texture *)tex->user_ptr; tex_mode = pt->valid; } } { int i; if (tex_mode) { //printf("Binding texture %d\n", pt->tex_id); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, pt->tex_id); } #if 0 { float v1[3], n[3], v2[3]; glBegin(GL_LINES); for (i = 0; i < 3; ++i) { lib3ds_vector_copy(v1, mesh->vertices[f->points[i]]); glVertex3fv(v1); lib3ds_vector_copy(n, normalL[3*p+i]); lib3ds_vector_scalar(n, 10.f); lib3ds_vector_add(v2, v1, n); glVertex3fv(v2); } glEnd(); } #endif glBegin(GL_TRIANGLES); for (i = 0; i < 3; ++i) { glNormal3fv(normalL[3*p+i]); if (tex_mode) { glTexCoord2f( mesh->texcos[mesh->faces[p].index[i]][1]*pt->scale_x, pt->scale_y - mesh->texcos[mesh->faces[p].index[i]][0]*pt->scale_y); } glVertex3fv(mesh->vertices[mesh->faces[p].index[i]]); } glEnd(); if (tex_mode) glDisable(GL_TEXTURE_2D); } } free(normalL); } glEndList(); } if (mesh->user_id) { glPushMatrix(); glMultMatrixf(&node->matrix[0][0]); glTranslatef(-n->pivot[0], -n->pivot[1], -n->pivot[2]); glCallList(mesh->user_id); /* glutSolidSphere(50.0, 20,20); */ glPopMatrix(); if (flush) glFlush(); } } } /*! * Update information about a light. Try to find corresponding nodes * if possible, and copy values from nodes into light struct. */ static void light_update(Lib3dsLight *l) { Lib3dsNode *ln, *sn; ln = lib3ds_file_node_by_name(file, l->name, LIB3DS_NODE_SPOTLIGHT); sn = lib3ds_file_node_by_name(file, l->name, LIB3DS_NODE_SPOTLIGHT_TARGET); if (ln != NULL) { Lib3dsSpotlightNode *n = (Lib3dsSpotlightNode*)ln; memcpy(l->color, n->color, 3 * sizeof(float)); memcpy(l->position, n->pos, 3 * sizeof(float)); } if (sn != NULL) { Lib3dsTargetNode *n = (Lib3dsTargetNode*)sn; memcpy(l->target, n->pos, 3* sizeof(float)); } } static void draw_bounds(float tgt[3]) { double cx, cy, cz; double lx, ly, lz; lx = sx / 10.; ly = sy / 10.; lz = sz / 10.; cx = tgt[0]; cy = tgt[1]; cz = tgt[2]; glDisable(GL_LIGHTING); glColor4fv(white); glBegin(GL_LINES); glVertex3f(bmin[0], bmin[1], bmin[2]); glVertex3f(bmax[0], bmin[1], bmin[2]); glVertex3f(bmin[0], bmax[1], bmin[2]); glVertex3f(bmax[0], bmax[1], bmin[2]); glVertex3f(bmin[0], bmin[1], bmax[2]); glVertex3f(bmax[0], bmin[1], bmax[2]); glVertex3f(bmin[0], bmax[1], bmax[2]); glVertex3f(bmax[0], bmax[1], bmax[2]); glVertex3f(bmin[0], bmin[1], bmin[2]); glVertex3f(bmin[0], bmax[1], bmin[2]); glVertex3f(bmax[0], bmin[1], bmin[2]); glVertex3f(bmax[0], bmax[1], bmin[2]); glVertex3f(bmin[0], bmin[1], bmax[2]); glVertex3f(bmin[0], bmax[1], bmax[2]); glVertex3f(bmax[0], bmin[1], bmax[2]); glVertex3f(bmax[0], bmax[1], bmax[2]); glVertex3f(bmin[0], bmin[1], bmin[2]); glVertex3f(bmin[0], bmin[1], bmax[2]); glVertex3f(bmax[0], bmin[1], bmin[2]); glVertex3f(bmax[0], bmin[1], bmax[2]); glVertex3f(bmin[0], bmax[1], bmin[2]); glVertex3f(bmin[0], bmax[1], bmax[2]); glVertex3f(bmax[0], bmax[1], bmin[2]); glVertex3f(bmax[0], bmax[1], bmax[2]); glVertex3f(cx - size / 32, cy, cz); glVertex3f(cx + size / 32, cy, cz); glVertex3f(cx, cy - size / 32, cz); glVertex3f(cx, cy + size / 32, cz); glVertex3f(cx, cy, cz - size / 32); glVertex3f(cx, cy, cz + size / 32); glEnd(); glColor4fv(red); glBegin(GL_LINES); glVertex3f(0., 0., 0.); glVertex3f(lx, 0., 0.); glEnd(); glColor4fv(green); glBegin(GL_LINES); glVertex3f(0., 0., 0.); glVertex3f(0., ly, 0.); glEnd(); glColor4fv(blue); glBegin(GL_LINES); glVertex3f(0., 0., 0.); glVertex3f(0., 0., lz); glEnd(); glEnable(GL_LIGHTING); } static void draw_light(const GLfloat *pos, const GLfloat *color) { glMaterialfv(GL_FRONT, GL_EMISSION, color); glPushMatrix(); glTranslatef(pos[0], pos[1], pos[2]); glScalef(size / 20, size / 20, size / 20); glCallList(lightList); glPopMatrix(); } /*! * Main display function; called whenever the scene needs to be redrawn. */ static void display(void) { Lib3dsTargetNode *t; Lib3dsCameraNode *c; float fov, roll; float near, far, dist; float *campos; float *tgt; float M[4][4]; int camidx; Lib3dsCamera *cam; float v[3]; Lib3dsNode *p; if (file != NULL && file->background.use_solid) glClearColor(file->background.solid_color[0], file->background.solid_color[1], file->background.solid_color[2], 1.); /* TODO: fog */ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (anti_alias) glEnable(GL_POLYGON_SMOOTH); else glDisable(GL_POLYGON_SMOOTH); if (!file) { return; } glLightModelfv(GL_LIGHT_MODEL_AMBIENT, file->ambient); c = (Lib3dsCameraNode*)lib3ds_file_node_by_name(file, camera, LIB3DS_NODE_CAMERA); t = (Lib3dsTargetNode*)lib3ds_file_node_by_name(file, camera, LIB3DS_NODE_CAMERA_TARGET); if (t != NULL) { tgt = t->pos; } if (c != NULL) { fov = c->fov; roll = c->roll; campos = c->pos; } if ((camidx = lib3ds_file_camera_by_name(file, camera)) == -1) return; cam = file->cameras[camidx]; near = cam->near_range; far = cam->far_range; if (c == NULL || t == NULL) { if (c == NULL) { fov = cam->fov; roll = cam->roll; campos = cam->position; } if (t == NULL) tgt = cam->target; } glMatrixMode(GL_PROJECTION); glLoadIdentity(); /* KLUDGE alert: OpenGL can't handle a near clip plane of zero, * so if the camera's near plane is zero, we give it a small number. * In addition, many .3ds files I've seen have a far plane that's * much too close and the model gets clipped away. I haven't found * a way to tell OpenGL not to clip the far plane, so we move it * further away. A factor of 10 seems to make all the models I've * seen visible. */ if (near <= 0.) near = far * .001; gluPerspective(fov, 1.0*gl_width / gl_height, 1, 10000); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(-90, 1.0, 0, 0); /* User rotates the view about the target point */ lib3ds_vector_sub(v, tgt, campos); dist = lib3ds_vector_length(v); glTranslatef(0., dist, 0.); glRotatef(view_rotx, 1., 0., 0.); glRotatef(view_roty, 0., 1., 0.); glRotatef(view_rotz, 0., 0., 1.); glTranslatef(0., -dist, 0.); lib3ds_matrix_camera(M, campos, tgt, roll); glMultMatrixf(&M[0][0]); /* Lights. Set them from light nodes if possible. If not, use the * light objects directly. */ { static const GLfloat a[] = {0.0f, 0.0f, 0.0f, 1.0f}; static GLfloat c[] = {1.0f, 1.0f, 1.0f, 1.0f}; static GLfloat p[] = {0.0f, 0.0f, 0.0f, 1.0f}; Lib3dsLight *l; int i; int li = GL_LIGHT0; for (i = 0; i < file->nlights; ++i) { l = file->lights[i]; glEnable(li); light_update(l); c[0] = l->color[0]; c[1] = l->color[1]; c[2] = l->color[2]; glLightfv(li, GL_AMBIENT, a); glLightfv(li, GL_DIFFUSE, c); glLightfv(li, GL_SPECULAR, c); p[0] = l->position[0]; p[1] = l->position[1]; p[2] = l->position[2]; glLightfv(li, GL_POSITION, p); if (l->spot_light) { p[0] = l->target[0] - l->position[0]; p[1] = l->target[1] - l->position[1]; p[2] = l->target[2] - l->position[2]; glLightfv(li, GL_SPOT_DIRECTION, p); } ++li; } } if (show_object) { for (p = file->nodes; p != 0; p = p->next) { render_node(p); } } if (show_bounds) draw_bounds(tgt); if (show_cameras) { int i; for (i = 0; i < file->ncameras; ++i) { cam = file->cameras[i]; lib3ds_matrix_camera(M, cam->position, cam->target, cam->roll); lib3ds_matrix_inv(M); glPushMatrix(); glMultMatrixf(&M[0][0]); glScalef(size / 20, size / 20, size / 20); glCallList(cameraList); glPopMatrix(); } } if (show_lights) { Lib3dsLight *light; int i; for (i = 0; i < file->nlights; ++i) { light = file->lights[i]; draw_light(light->position, light->color); } glMaterialfv(GL_FRONT, GL_EMISSION, black); } glutSwapBuffers(); } /*! * */ static void reshape(int w, int h) { gl_width = w; gl_height = h; glViewport(0, 0, w, h); } /*! * */ static void keyboard(unsigned char key, int x, int y) { switch (key) { case 27: exit(0); break; case 'h': set_halt(!halt); break; case 'a': anim_rotz += .05; break; case 'A': anim_rotz -= .05; break; case 'r': view_rotx = view_roty = view_rotz = anim_rotz = 0.; break; case 'z': view_roty += 5.; break; case 'Z': view_roty -= 5.; break; case 'b': show_bounds = !show_bounds; break; case 'o': show_object = !show_object; break; case '\001': anti_alias = !anti_alias; break; } lib3ds_file_eval(file, current_frame); glutPostRedisplay(); } /*! * Respond to mouse buttons. Action depends on current operating mode. */ static void mouse_cb(int button, int state, int x, int y) { mx = x; my = y; switch (button) { case GLUT_LEFT_BUTTON: switch (runMode) { case ROTATING: rotating = state == GLUT_DOWN; break; default: break; } break; default: break; } } /*! * Respond to mouse motions; left button rotates the image or performs * other action according to current operating mode. */ static void drag_cb(int x, int y) { if (rotating) { view_rotz += MOUSE_SCALE * (x - mx); view_rotx += MOUSE_SCALE * (y - my); mx = x; my = y; glutPostRedisplay(); } } /*! * Create camera and light icons */ static void create_icons() { GLUquadricObj *qobj; #define CBX .25 // camera body dimensions #define CBY 1.5 #define CBZ 1. qobj = gluNewQuadric(); gluQuadricDrawStyle(qobj, GLU_FILL); gluQuadricNormals(qobj, GLU_SMOOTH); cameraList = glGenLists(1); glNewList(cameraList, GL_COMPILE); glMaterialfv(GL_FRONT, GL_AMBIENT, dgrey); glMaterialfv(GL_FRONT, GL_DIFFUSE, lgrey); glMaterialfv(GL_FRONT, GL_SPECULAR, black); glEnable(GL_CULL_FACE); solidBox(CBX, CBY, CBZ); glPushMatrix(); glTranslatef(0., .9, 1.8); glRotatef(90., 0., 1., 0.); solidCylinder(1., CBX*2, 12); glTranslatef(0., -1.8, 0.); solidCylinder(1., CBX*2, 12); glPopMatrix(); glDisable(GL_CULL_FACE); glPushMatrix(); glTranslated(0., CBY, 0.); glRotated(-90., 1., 0., 0.); gluCylinder(qobj, .2, .5, 1., 12, 1); glPopMatrix(); glEndList(); lightList = glGenLists(1); glNewList(lightList, GL_COMPILE); glPushMatrix(); glMaterialfv(GL_FRONT, GL_AMBIENT, dgrey); glMaterialfv(GL_FRONT, GL_DIFFUSE, dgrey); glMaterialfv(GL_FRONT, GL_SPECULAR, grey); glEnable(GL_CULL_FACE); gluSphere(qobj, .5, 12., 6.); glRotated(180., 1., 0., 0.); glMaterialfv(GL_FRONT, GL_EMISSION, dgrey); gluCylinder(qobj, .2, .2, 1., 12, 1); glPopMatrix(); glEndList(); } void decompose_datapath(const char *fn) { const char *ptr = strrchr(fn, '/'); if (ptr == NULL) { strcpy(datapath, "."); strcpy(filename, fn); } else { strcpy(filename, ptr + 1); strcpy(datapath, fn); datapath[ptr - fn] = '\0'; } } /*! * */ int main(int argc, char** argv) { char *progname = argv[0]; glutInit(&argc, argv); for (++argv; --argc > 0; ++argv) { if (strcmp(*argv, "-help") == 0 || strcmp(*argv, "--help") == 0) { fputs("View a 3DS model file using OpenGL.\n", stderr); fputs("Usage: 3dsplayer [-nodb|-aa|-flush] <filename>\n", stderr); #ifndef USE_SDL fputs("Texture rendering is not available; install SDL_image and recompile.\n", stderr); #endif exit(0); } else if (strcmp(*argv, "-nodb") == 0) dbuf = 0; else if (strcmp(*argv, "-aa") == 0) anti_alias = 1; else if (strcmp(*argv, "-flush") == 0) flush = 1; else { filepath = *argv; decompose_datapath(filepath); } } if (filepath == NULL) { fputs("3dsplayer: Error: No 3DS file specified\n", stderr); exit(1); } glutInitDisplayMode(GLUT_DEPTH | GLUT_RGB | (dbuf ? GLUT_DOUBLE : 0)); glutInitWindowSize(500, 500); glutInitWindowPosition(100, 100); glutCreateWindow(filepath != NULL ? Basename(filepath) : progname); init(); create_icons(); load_model(); build_menu(); glutAttachMenu(2); glutDisplayFunc(display); glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutMouseFunc(mouse_cb); glutMotionFunc(drag_cb); glutTimerFunc(1000 / FRAMES_PER_SECOND, timer_cb, 0); glutMainLoop(); return(0); } /* A few small utilities, so generic that they probably * don't even belong in this file. */ /*! * Render a box, centered at 0,0,0 * * Box may be rendered with face culling enabled. */ static void solidBox(double bx, double by, double bz) { glBegin(GL_POLYGON); glNormal3d(0., 0., 1.); glVertex3d(bx, by, bz); glVertex3d(-bx, by, bz); glVertex3d(-bx, -by, bz); glVertex3d(bx, -by, bz); glEnd(); glBegin(GL_POLYGON); glNormal3d(0., 0., -1.); glVertex3d(-bx, by, -bz); glVertex3d(bx, by, -bz); glVertex3d(bx, -by, -bz); glVertex3d(-bx, -by, -bz); glEnd(); glBegin(GL_POLYGON); glNormal3d(0., -1., 0.); glVertex3d(-bx, by, bz); glVertex3d(bx, by, bz); glVertex3d(bx, by, -bz); glVertex3d(-bx, by, -bz); glEnd(); glBegin(GL_POLYGON); glNormal3d(0., -1., 0.); glVertex3d(bx, -by, bz); glVertex3d(-bx, -by, bz); glVertex3d(-bx, -by, -bz); glVertex3d(bx, -by, -bz); glEnd(); glBegin(GL_POLYGON); glNormal3d(1., 0., 0.); glVertex3d(bx, by, bz); glVertex3d(bx, -by, bz); glVertex3d(bx, -by, -bz); glVertex3d(bx, by, -bz); glEnd(); glBegin(GL_POLYGON); glNormal3d(-1., 0., 0.); glVertex3d(-bx, by, -bz); glVertex3d(-bx, -by, -bz); glVertex3d(-bx, -by, bz); glVertex3d(-bx, by, bz); glEnd(); } /*! * Render a cylinder with end caps, along the Z axis centered at 0,0,0 * * Cylinder may be rendered with face culling enabled. */ static void solidCylinder(double r, double h, int slices) { GLUquadricObj *qobj = gluNewQuadric(); gluQuadricDrawStyle(qobj, GLU_FILL); gluQuadricNormals(qobj, GLU_SMOOTH); glPushMatrix(); glTranslated(0., 0., -h / 2); gluCylinder(qobj, r, r, h, slices, 1); glPushMatrix(); glRotated(180., 1., 0., 0.); gluDisk(qobj, 0., r, slices, 1); glPopMatrix(); glTranslated(0., 0., h); gluDisk(qobj, 0., r, slices, 1); glPopMatrix(); } static const char * Basename(const char *filename) { const char *ptr = strrchr(filename, '/'); return ptr != NULL ? ptr + 1 : filename; } /* * This module is a crude front end to the GLUT menu system, allowing for * slightly more sophisticated callbacks. */ #include <stdio.h> #define MAX_CALLBACKS 100 typedef struct { void (*cb)(int, int, void *); void *client; } Callback; static Callback callbacks[MAX_CALLBACKS]; static int ncb = 0; /*! * Register a callback, returning an integer value suitable for * passing to glutAddMenuEntry() * * \param cb Callback function to be called. * \param client Data to be passed to the callback. * * \return integer callback id */ static int callback(void (*cb)(int, int, void *client), void *client) { if (ncb == 0) { int i; for (i = 0; i < NA(callbacks); ++i) callbacks[i].cb = NULL; } else if (ncb >= NA(callbacks)) { fprintf(stderr, "callback() out of callbacks, try changing MAX_CALLBACKS\n"); } callbacks[ncb].cb = cb; callbacks[ncb].client = client; return ncb++; } /*! * Call the indexed callback. * * \param idx Callback index. * \param data Data to be passed to the callback */ static void call_callback(int idx, int data) { if (idx >= 0 && idx < NA(callbacks) && callbacks[idx].cb != NULL) callbacks[idx].cb(idx, data, callbacks[idx].client); }
3dsplay.cpp
最新推荐文章于 2017-04-07 16:58:35 发布