/* Demostration program for libv3d. Uses libv3d and glut to load and display a given V3D file. */ #include #include #include #include #include #include /* Include this for the GL Utility Toolkit. */ #include #include /* Include this for libv3d api prototypes. */ static int V3DDemoLoadCB(void *data, int c, int m); static void V3DDemoExtranousDataCB( v3d_model_struct *model, const char *string, void *client_data ); static void V3DDemoGLUTInitCB(void); static void V3DDemoGLUTDisplayCB(void); static void V3DDemoGLUTReshapeCB(int w, int h); static void V3DDemoGLUTKeyboardCB(unsigned char key, int x, int y); static void V3DDemoGLUTCloseCB(void); /* Globals to keep track of loaded V3D data that's been turned into * GL display lists. */ static int total_display_lists; static GLuint *display_list; static v3d_glresource_struct *glres; /* Make-shift camera structure. */ typedef struct { double x, y, z; double heading, pitch, bank; /* In radians. */ double clip_near, clip_far; double field_of_view; /* Along Z axis. */ } v3ddemo_camera_struct; static v3ddemo_camera_struct v3ddemo_camera; /* Make-shift light structure. */ typedef struct { int enabled; /* True if enabled. */ double x, y, z; } v3ddemo_light_struct; #define TOTAL_LIGHTS 8 static v3ddemo_light_struct v3ddemo_light[TOTAL_LIGHTS]; /* Force no lighting even if model specifies lighting. */ static int force_no_lighting = 0; #ifndef PI # define PI 3.14159265359 #endif #define ISBLANK(c) (((c) == ' ') || ((c) == '\t')) #define MAX(a,b) (((a) > (b)) ? (a) : (b)) #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #define CLIP(a,l,h) (MIN(MAX((a),(l)),(h))) #define RADTODEG(r) ((r) * 180.0 / PI) #define DEGTORAD(d) ((d) * PI / 180.0) /* * Load progress callback used for V3DLoadModel(). */ static int V3DDemoLoadCB(void *data, int c, int m) { if(m <= 0) return(0); printf( "\rLoaded %i bytes (%.0f%%)... ", c, ((double)c * 100.0 / (double)m) ); fflush(stdout); return(0); } /* * Processes a piece of extranous data string with respect to the * given model. * * This function is called whenever third party data is encountered * when loading the the V3D file. * * In this function we will parse for comment characters that have * special meaning (ie "$view 4 ..."). */ static void V3DDemoExtranousDataCB( v3d_model_struct *model, const char *string, void *client_data ) { int i; char *strptr; char parm[256]; if((model == NULL) || (string == NULL)) return; /* Seek past blank characters. */ while(ISBLANK(*string)) string++; /* Give up if this is not a comment. */ if((*string) != '#') return; /* Seek past blank characters. */ string++; while(ISBLANK(*string)) string++; /* Is next character the special token? */ if((*string) != '$') { /* This is just a regular comment then, give up. */ return; } else { string++; while(ISBLANK(*string)) string++; } /* Copy parm from string. */ strncpy(parm, string, 256); parm[256 - 1] = '\0'; strptr = parm; while(!ISBLANK(*strptr) && ((*strptr) != '\0')) strptr++; (*strptr) = '\0'; /* Seek string to first argument. */ while(!ISBLANK(*string) && ((*string) != '\0')) string++; while(ISBLANK(*string)) string++; /* Handle by parm. */ if(!strcasecmp(parm, "view4")) { /* This is the 3D view that Vertex3D saves position and * attributes of. We'll use this as the camera position. */ int sc, st = 16; double value[16]; sc = sscanf(string, "%lf %lf %lf %lf %lf %lf %lf %lf\ %lf %lf %lf %lf %lf %lf %lf %lf", &value[0], &value[1], &value[2], &value[3], &value[4], &value[5], &value[6], &value[7], &value[8], &value[9], &value[10], &value[11], &value[12], &value[13], &value[14], &value[15] ); for(i = sc; i < st; i++) value[i] = 0.0; v3ddemo_camera.x = value[0]; v3ddemo_camera.y = value[1]; v3ddemo_camera.z = value[2]; v3ddemo_camera.heading = DEGTORAD(value[3]); v3ddemo_camera.pitch = DEGTORAD(value[4]); v3ddemo_camera.bank = DEGTORAD(value[5]); v3ddemo_camera.clip_near = value[6]; v3ddemo_camera.clip_far = value[7]; v3ddemo_camera.field_of_view = DEGTORAD(value[8]); } else if(!strcasecmp(parm, "light")) { /* This is a light that Vertex3D saves position and * attributes of. */ int light_num; v3ddemo_light_struct *light_ptr; int sc, st = 8; double value[8]; sc = sscanf(string, "%lf %lf %lf %lf %lf %lf %lf %lf", &value[0], &value[1], &value[2], &value[3], &value[4], &value[5], &value[6], &value[7] ); for(i = sc; i < st; i++) value[i] = 0.0; light_num = (int)value[0]; if((light_num >= 0) && (light_num < TOTAL_LIGHTS)) { light_ptr = &v3ddemo_light[light_num]; light_ptr->enabled = (int)value[1]; light_ptr->x = (int)value[2]; light_ptr->y = (int)value[3]; light_ptr->z = (int)value[4]; } } return; } /* * GLUT initialize callback. */ static void V3DDemoGLUTInitCB(void) { glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glEnable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); glShadeModel(GL_SMOOTH); glEnable(GL_CULL_FACE); glFrontFace(GL_CCW); glCullFace(GL_BACK); glDisable(GL_COLOR_MATERIAL); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1); glClearColor(0.0, 0.0, 0.0, 0.0); glClearDepth(1.0); return; } /* * GLUT display callback. */ static void V3DDemoGLUTDisplayCB(void) { int i, model_num; GLuint list; GLboolean lighting_enabled; v3ddemo_light_struct *light_ptr; /* Set up camera. */ glLoadIdentity(); gluLookAt( v3ddemo_camera.x, v3ddemo_camera.z, -v3ddemo_camera.y, (sin(v3ddemo_camera.heading) + v3ddemo_camera.x), (-sin(v3ddemo_camera.pitch) + v3ddemo_camera.z), -(cos(v3ddemo_camera.heading) + v3ddemo_camera.y), 0.0, 1.0, 0.0 ); /* Set up lighting. */ glDisable(GL_LIGHTING); lighting_enabled = GL_FALSE; for(i = 0; i < TOTAL_LIGHTS; i++) { light_ptr = &v3ddemo_light[i]; if(light_ptr->enabled) { GLenum gl_light_num = GL_LIGHT0 + i; GLfloat value[4]; value[0] = 1.0; value[1] = 1.0; value[2] = 1.0; value[3] = 1.0; glLightfv(gl_light_num, GL_AMBIENT, value); value[0] = 1.0; value[1] = 1.0; value[2] = 1.0; value[3] = 1.0; glLightfv(gl_light_num, GL_DIFFUSE, value); value[0] = 1.0; value[1] = 1.0; value[2] = 1.0; value[3] = 1.0; glLightfv(gl_light_num, GL_SPECULAR, value); value[0] = light_ptr->x; value[1] = light_ptr->z; value[2] = -light_ptr->y; value[3] = 0.0; glLightfv(gl_light_num, GL_POSITION, value); value[0] = 0.0; value[1] = -1.0; value[2] = -0.0; value[3] = 0.0; glLightfv(gl_light_num, GL_SPOT_DIRECTION, value); value[0] = 180.0; glLightfv(gl_light_num, GL_SPOT_CUTOFF, value); value[0] = 0.0; glLightfv(gl_light_num, GL_SPOT_EXPONENT, value); value[0] = 1.0; glLightfv(gl_light_num, GL_CONSTANT_ATTENUATION, value); value[0] = 0.0; glLightfv(gl_light_num, GL_LINEAR_ATTENUATION, value); value[0] = 0.0; glLightfv(gl_light_num, GL_QUADRATIC_ATTENUATION, value); glEnable(gl_light_num); if(!lighting_enabled && !force_no_lighting) { glEnable(GL_LIGHTING); lighting_enabled = GL_TRUE; } } else { int gl_light_num = GL_LIGHT0 + i; glDisable(gl_light_num); } } /* Set up global lighting. */ if(lighting_enabled && !force_no_lighting) { GLfloat value[4]; GLenum face = GL_FRONT; /* Reset initial normal. */ glNormal3d(0.0, 1.0, 0.0); /* Reset initial material values. */ value[0] = 0.2; value[1] = 0.2; value[2] = 0.2; value[3] = 1.0; glMaterialfv(face, GL_AMBIENT, value); value[0] = 0.8; value[1] = 0.8; value[2] = 0.8; value[3] = 1.0; glMaterialfv(face, GL_DIFFUSE, value); value[0] = 0.0; value[1] = 0.0; value[2] = 0.0; value[3] = 1.0; glMaterialfv(face, GL_SPECULAR, value); value[0] = 0.0 * 128.0; glMaterialfv(face, GL_SHININESS, value); value[0] = 0.0; value[1] = 0.0; value[2] = 0.0; value[3] = 1.0; glMaterialfv(face, GL_EMISSION, value); /* Global ambient lighting. */ value[0] = 0.0; value[1] = 0.0; value[2] = 0.0; value[3] = 1.0; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, value); glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE); glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE /* One side, since culling. */ ); glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SINGLE_COLOR); } /* Clear buffers. */ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* Draw each model. */ for(model_num = 0; model_num < total_display_lists; model_num++) { list = display_list[model_num]; if(list == 0) continue; glCallList(list); } glutSwapBuffers(); /* Check for error? */ if(1) { GLenum error_code = glGetError(); if(error_code != GL_NO_ERROR) { printf("Got GL error %i (", error_code); printf("%s", gluErrorString(error_code)); printf(")\n"); } } return; } /* * GLUT reshape callback. */ static void V3DDemoGLUTReshapeCB(int w, int h) { /* Update viewport size. */ glViewport(0, 0, (GLsizei)w, (GLsizei)h); /* Update projection matrix to camera aperature. */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective( RADTODEG(v3ddemo_camera.field_of_view), (GLfloat)w / (GLfloat)h, v3ddemo_camera.clip_near, v3ddemo_camera.clip_far ); /* Update model view matrix to camera position. */ glMatrixMode(GL_MODELVIEW); glLoadIdentity(); return; } /* * GLUT keyboard callback. */ static void V3DDemoGLUTKeyboardCB(unsigned char key, int x, int y) { switch(key) { case 'a': v3ddemo_camera.heading -= 0.05 * PI; while(v3ddemo_camera.heading < (0.0 * PI)) v3ddemo_camera.heading += 2.0 * PI; glutPostRedisplay(); break; case 'd': v3ddemo_camera.heading += 0.05 * PI; while(v3ddemo_camera.heading >= (2.0 * PI)) v3ddemo_camera.heading -= 2.0 * PI; glutPostRedisplay(); break; case 'w': v3ddemo_camera.x += sin(v3ddemo_camera.heading) * 1.0; v3ddemo_camera.y += cos(v3ddemo_camera.heading) * 1.0; glutPostRedisplay(); break; case 'x': v3ddemo_camera.x -= sin(v3ddemo_camera.heading) * 1.0; v3ddemo_camera.y -= cos(v3ddemo_camera.heading) * 1.0; glutPostRedisplay(); break; case 'e': v3ddemo_camera.pitch -= 0.05 * PI; while(v3ddemo_camera.pitch < (0.0 * PI)) v3ddemo_camera.pitch += 2.0 * PI; if((v3ddemo_camera.pitch > (0.5 * PI)) && (v3ddemo_camera.pitch <= (1.0 * PI)) ) v3ddemo_camera.pitch = 0.5 * PI; if((v3ddemo_camera.pitch < (1.5 * PI)) && (v3ddemo_camera.pitch >= (1.0 * PI)) ) v3ddemo_camera.pitch = 1.5 * PI; glutPostRedisplay(); break; case 'c': v3ddemo_camera.pitch += 0.05 * PI; while(v3ddemo_camera.pitch >= (2.0 * PI)) v3ddemo_camera.pitch -= 2.0 * PI; if((v3ddemo_camera.pitch > (0.5 * PI)) && (v3ddemo_camera.pitch <= (1.0 * PI)) ) v3ddemo_camera.pitch = 0.5 * PI; if((v3ddemo_camera.pitch < (1.5 * PI)) && (v3ddemo_camera.pitch >= (1.0 * PI)) ) v3ddemo_camera.pitch = 1.5 * PI; glutPostRedisplay(); break; case 'q': v3ddemo_camera.z += 1.0; glutPostRedisplay(); break; case 'z': v3ddemo_camera.z -= 1.0; glutPostRedisplay(); break; case 'l': if(force_no_lighting) force_no_lighting = 0; else force_no_lighting = 1; glutPostRedisplay(); break; case 27: /* Escape. */ V3DDemoGLUTCloseCB(); exit(0); break; } return; } /* * GLUT close callback. */ static void V3DDemoGLUTCloseCB(void) { int i; /* Destroy the display lists. */ for(i = 0; i < total_display_lists; i++) { if(display_list[i] != 0) { glDeleteLists(display_list[i], 1); display_list[i] = 0; } } if(display_list != NULL) { free(display_list); display_list = NULL; total_display_lists = 0; } /* Deallocate the GL resource structure. */ V3DGLResourceDelete(glres); glres = NULL; return; } int main(int argc, char *argv[]) { FILE *fp; int i, status; void **mh_item = NULL; int total_mh_items = 0; v3d_model_struct **model = NULL; int total_models = 0; v3d_glinterprite_struct *glinterp; if(argc < 2) { printf("Usage: %s \n", argv[0]); return(1); } /* Open the V3D file (first argument). */ fp = fopen(argv[1], "rb"); if(fp == NULL) return(1); /* Load the V3D file by passing the file pointer, on success * the contents will be loaded into the given array of * model header items and models. * * We pass the pointer to our load callback function * V3DDemoLoadCB() which will be called several times during loading * to update the progress (note that we left client_data NULL as * we didn't need one for this demo). */ status = V3DLoadModel( NULL, fp, &mh_item, &total_mh_items, &model, &total_models, NULL, /* No client data. */ V3DDemoLoadCB /* Progress callback. */ ); /* Print a new line, because the progress callback dosen't after * completion. */ printf("\n"); /* Close the V3D file. */ fclose(fp); /* Error occured while loading the V3D file? */ if(status) { /* Delete any loaded data and give up. */ V3DMHListDeleteAll(&mh_item, &total_mh_items); V3DModelListDeleteAll(&model, &total_models); return(1); } /* At this point the V3D data has been loaded successfully * into the given model header items and models pointer arrays. * * We are going to initialize glut and then convert them into * GL commands in a display list. */ printf( "Loaded: %i header items %i models\n", total_mh_items, total_models ); /* Initialize glut. */ glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(640, 480); glutCreateWindow(argv[1]); /* Now that we have a GL context, we can convert the loaded * V3D models into GL commands stored in a display list. * There are two ways to do this, the hard way and the simple way. * Here we will do it the simple way by using libv3d's API. */ /* First we need to create a V3D to GL interpritations list, this * is a structure with a bunch of members that determine how * V3D data is `interprited' to GL data. */ glinterp = V3DGLInterpriteNew(); if(glinterp != NULL) { /* Here is where all the configuration takes place! */ /* Set the flags to indicate which fields in the glinterp * structure are going to be defined properly. */ glinterp->flags = (V3D_GLFLAG_COORDINATE_AXIS | V3D_GLFLAG_TEXTURE_KEEP | V3D_GLFLAG_ALLOW_TRANSLATIONS | V3D_GLFLAG_ALLOW_ROTATIONS | V3D_GLFLAG_FLIP_WINDING | V3D_GLFLAG_PASS_NORMALS | V3D_GLFLAG_UNITLIZE_NORMALS | V3D_GLFLAG_PASS_TEXCOORDS | V3D_GLFLAG_TEXTURE_NAME_CASE_SENSITIVE | V3D_GLFLAG_MATERIAL_PROPERTIES | V3D_GLFLAG_FACES | V3D_GLFLAG_ENABLE_BLENDING | V3D_GLFLAG_SET_BLEND_FUNC ); /* V3D data is in scientific coordinates while GL uses * GL coordinates, need to convert. */ glinterp->coordinate_axis = V3D_GLCOORDINATE_AXIS_SCIENTIFIC; /* Let's just keep textures loaded even if they are not * binded during the conversion. */ glinterp->texture_keep = V3D_GLTEXTURE_KEEP_ALWAYS; /* Allow translate and rotate primitives to be parsed. */ glinterp->allow_translations = TRUE; glinterp->allow_rotations = TRUE; /* Most V3D surfaces are winded clockwise, while GL uses * counter-clockwise by default. So we need to flip winding. */ glinterp->flip_winding = TRUE; /* Pass normals when needed. */ glinterp->pass_normals = V3D_GLPASS_NORMALS_AS_NEEDED; /* Yes, calculate normals to unit length before passing to * GL. */ glinterp->unitlize_normals = TRUE; /* Pass texcoords when needed. */ glinterp->pass_texcoords = V3D_GLPASS_TEXCOORDS_AS_NEEDED; /* Texture names are case insensitive. */ glinterp->texture_name_case_sensitive = FALSE; /* Use glMaterial() and glColor() when a color primitive is * encountered. */ glinterp->material_properties = V3D_GLPASS_MATERIAL_PROPERTIES_WITH_COLOR; /* Just account for front faces, since culling will be on. */ glinterp->faces = V3D_GLFACES_FRONT; /* Yes, put glEnable(GL_BLEND) and glDisable(GL_BLEND) calls * as needed (when a color alpha is < 1.0) in to the display * list. */ glinterp->enable_blending = V3D_GLENABLE_BLENDING_AS_NEEDED; /* No, let us set the GL blend function. */ glinterp->set_blend_func = 0; } /* Now we need to create a GL resources structure that will * hold the GL resources allocated by the upcomming interpritation * of the V3D to GL data. */ glres = V3DGLResourceNewFromModelData( (const v3d_glinterprite_struct *)glinterp, mh_item, total_mh_items, model, total_models ); /* We don't need the GL interpritation structure anymore. */ V3DGLInterpriteDelete(glinterp); glinterp = NULL; /* Create a new set of display lists for each model. */ total_display_lists = total_models; display_list = (GLuint *)calloc( total_display_lists, sizeof(GLuint) ); if(display_list == NULL) { total_display_lists = 0; } /* Itterate through each display list and corresponding model. */ for(i = 0; i < total_display_lists; i++) { display_list[i] = glGenLists(1); if(display_list[i] != 0) { glNewList(display_list[i], GL_COMPILE); /* Interprite the V3D model into GL commands. */ V3DGLProcessModelExtra( glres, model[i], NULL, V3DDemoExtranousDataCB ); glEndList(); } } /* Deallocate the loaded header items and models, we no * longer need them. */ V3DMHListDeleteAll(&mh_item, &total_mh_items); V3DModelListDeleteAll(&model, &total_models); V3DDemoGLUTInitCB(); glutDisplayFunc(V3DDemoGLUTDisplayCB); glutReshapeFunc(V3DDemoGLUTReshapeCB); glutKeyboardFunc(V3DDemoGLUTKeyboardCB); glutMainLoop(); /* Never reached, see V3DDemoGLUTCloseCB() for appropriate * procedure to deallocate the GL display list and GL * interpritation structure that we created. */ return(0); }