From ca1bda552d1cd1a6ddc911e535681a10b9c2d846 Mon Sep 17 00:00:00 2001 From: Brian Paul Date: Thu, 1 Oct 2009 12:58:36 -0600 Subject: progs/objviewer: Wavefront .obj file loader/viewer demo Adapted from code written by Nate Robins. See README.txt. --- progs/objviewer/glm.c | 1917 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1917 insertions(+) create mode 100644 progs/objviewer/glm.c (limited to 'progs/objviewer/glm.c') diff --git a/progs/objviewer/glm.c b/progs/objviewer/glm.c new file mode 100644 index 0000000000..7f36cdf28e --- /dev/null +++ b/progs/objviewer/glm.c @@ -0,0 +1,1917 @@ +/* + * GLM library. Wavefront .obj file format reader/writer/manipulator. + * + * Written by Nate Robins, 1997. + * email: ndr@pobox.com + * www: http://www.pobox.com/~ndr + */ + +/* includes */ +#include +#include +#include +#include +#include +#include "glm.h" +#include "readtex.h" + + +typedef unsigned char boolean; +#define TRUE 1 +#define FALSE 0 + + +/* Some files do not define M_PI... */ +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +/* defines */ +#define T(x) model->triangles[(x)] + + +/* enums */ +enum { X, Y, Z, W }; /* elements of a vertex */ + + +/* typedefs */ + +/* _GLMnode: general purpose node + */ +typedef struct _GLMnode { + uint index; + boolean averaged; + struct _GLMnode* next; +} GLMnode; + +/* strdup is actually not a standard ANSI C or POSIX routine + so implement a private one. OpenVMS does not have a strdup; Linux's + standard libc doesn't declare strdup by default (unless BSD or SVID + interfaces are requested). */ +static char * +stralloc(const char *string) +{ + char *copy; + + copy = malloc(strlen(string) + 1); + if (copy == NULL) + return NULL; + strcpy(copy, string); + return copy; +} + +/* private functions */ + +/* _glmMax: returns the maximum of two floats */ +static float +_glmMax(float a, float b) +{ + if (a > b) + return a; + return b; +} + +/* _glmAbs: returns the absolute value of a float */ +static float +_glmAbs(float f) +{ + if (f < 0) + return -f; + return f; +} + +/* _glmDot: compute the dot product of two vectors + * + * u - array of 3 floats (float u[3]) + * v - array of 3 floats (float v[3]) + */ +static float +_glmDot(float* u, float* v) +{ + assert(u); + assert(v); + + /* compute the dot product */ + return u[X] * v[X] + u[Y] * v[Y] + u[Z] * v[Z]; +} + +/* _glmCross: compute the cross product of two vectors + * + * u - array of 3 floats (float u[3]) + * v - array of 3 floats (float v[3]) + * n - array of 3 floats (float n[3]) to return the cross product in + */ +static void +_glmCross(float* u, float* v, float* n) +{ + assert(u); + assert(v); + assert(n); + + /* compute the cross product (u x v for right-handed [ccw]) */ + n[X] = u[Y] * v[Z] - u[Z] * v[Y]; + n[Y] = u[Z] * v[X] - u[X] * v[Z]; + n[Z] = u[X] * v[Y] - u[Y] * v[X]; +} + +/* _glmNormalize: normalize a vector + * + * n - array of 3 floats (float n[3]) to be normalized + */ +static void +_glmNormalize(float* n) +{ + float l; + + assert(n); + + /* normalize */ + l = (float)sqrt(n[X] * n[X] + n[Y] * n[Y] + n[Z] * n[Z]); + n[0] /= l; + n[1] /= l; + n[2] /= l; +} + +/* _glmEqual: compares two vectors and returns TRUE if they are + * equal (within a certain threshold) or FALSE if not. An epsilon + * that works fairly well is 0.000001. + * + * u - array of 3 floats (float u[3]) + * v - array of 3 floats (float v[3]) + */ +static boolean +_glmEqual(float* u, float* v, float epsilon) +{ + if (_glmAbs(u[0] - v[0]) < epsilon && + _glmAbs(u[1] - v[1]) < epsilon && + _glmAbs(u[2] - v[2]) < epsilon) + { + return TRUE; + } + return FALSE; +} + +/* _glmWeldVectors: eliminate (weld) vectors that are within an + * epsilon of each other. + * + * vectors - array of float[3]'s to be welded + * numvectors - number of float[3]'s in vectors + * epsilon - maximum difference between vectors + * + */ +static float* +_glmWeldVectors(float* vectors, uint* numvectors, float epsilon) +{ + float* copies; + uint copied; + uint i, j; + + copies = (float*)malloc(sizeof(float) * 3 * (*numvectors + 1)); + memcpy(copies, vectors, (sizeof(float) * 3 * (*numvectors + 1))); + + copied = 1; + for (i = 1; i <= *numvectors; i++) { + for (j = 1; j <= copied; j++) { + if (_glmEqual(&vectors[3 * i], &copies[3 * j], epsilon)) { + goto duplicate; + } + } + + /* must not be any duplicates -- add to the copies array */ + copies[3 * copied + 0] = vectors[3 * i + 0]; + copies[3 * copied + 1] = vectors[3 * i + 1]; + copies[3 * copied + 2] = vectors[3 * i + 2]; + j = copied; /* pass this along for below */ + copied++; + + duplicate: + /* set the first component of this vector to point at the correct + index into the new copies array */ + vectors[3 * i + 0] = (float)j; + } + + *numvectors = copied-1; + return copies; +} + +/* _glmFindGroup: Find a group in the model + */ +static GLMgroup* +_glmFindGroup(GLMmodel* model, char* name) +{ + GLMgroup* group; + + assert(model); + + group = model->groups; + while(group) { + if (!strcmp(name, group->name)) + break; + group = group->next; + } + + return group; +} + +/* _glmAddGroup: Add a group to the model + */ +static GLMgroup* +_glmAddGroup(GLMmodel* model, char* name) +{ + GLMgroup* group; + + group = _glmFindGroup(model, name); + if (!group) { + group = (GLMgroup*)malloc(sizeof(GLMgroup)); + group->name = stralloc(name); + group->material = 0; + group->numtriangles = 0; + group->triangles = NULL; + group->next = model->groups; + model->groups = group; + model->numgroups++; + } + + return group; +} + +/* _glmFindGroup: Find a material in the model + */ +static uint +_glmFindMaterial(GLMmodel* model, char* name) +{ + uint i; + + for (i = 0; i < model->nummaterials; i++) { + if (!strcmp(model->materials[i].name, name)) + goto found; + } + + /* didn't find the name, so set it as the default material */ + printf("_glmFindMaterial(): can't find material \"%s\".\n", name); + i = 0; + +found: + return i; +} + + +/* _glmDirName: return the directory given a path + * + * path - filesystem path + * + * The return value should be free'd. + */ +static char* +_glmDirName(char* path) +{ + char* dir; + char* s; + + dir = stralloc(path); + + s = strrchr(dir, '/'); + if (s) + s[1] = '\0'; + else + dir[0] = '\0'; + + return dir; +} + + +/* _glmReadMTL: read a wavefront material library file + * + * model - properly initialized GLMmodel structure + * name - name of the material library + */ +static void +_glmReadMTL(GLMmodel* model, char* name) +{ + FILE* file; + char* dir; + char* filename; + char buf[128], buf2[128]; + uint nummaterials, i; + GLMmaterial *mat; + + dir = _glmDirName(model->pathname); + filename = (char*)malloc(sizeof(char) * (strlen(dir) + strlen(name) + 1)); + strcpy(filename, dir); + strcat(filename, name); + free(dir); + + /* open the file */ + file = fopen(filename, "r"); + if (!file) { + fprintf(stderr, "_glmReadMTL() failed: can't open material file \"%s\".\n", + filename); + exit(1); + } + free(filename); + + /* count the number of materials in the file */ + nummaterials = 1; + while(fscanf(file, "%s", buf) != EOF) { + switch(buf[0]) { + case '#': /* comment */ + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + break; + case 'n': /* newmtl */ + fgets(buf, sizeof(buf), file); + nummaterials++; + sscanf(buf, "%s %s", buf, buf); + break; + default: + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + break; + } + } + + rewind(file); + + /* allocate memory for the materials */ + model->materials = (GLMmaterial*)calloc(nummaterials, sizeof(GLMmaterial)); + model->nummaterials = nummaterials; + + /* set the default material */ + for (i = 0; i < nummaterials; i++) { + model->materials[i].name = NULL; + model->materials[i].shininess = 0; + model->materials[i].diffuse[0] = 0.8; + model->materials[i].diffuse[1] = 0.8; + model->materials[i].diffuse[2] = 0.8; + model->materials[i].diffuse[3] = 1.0; + model->materials[i].ambient[0] = 0.2; + model->materials[i].ambient[1] = 0.2; + model->materials[i].ambient[2] = 0.2; + model->materials[i].ambient[3] = 0.0; + model->materials[i].specular[0] = 0.0; + model->materials[i].specular[1] = 0.0; + model->materials[i].specular[2] = 0.0; + model->materials[i].specular[3] = 0.0; + } + model->materials[0].name = stralloc("default"); + + /* now, read in the data */ + nummaterials = 0; + + mat = &model->materials[nummaterials]; + + while(fscanf(file, "%s", buf) != EOF) { + switch(buf[0]) { + case '#': /* comment */ + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + break; + case 'n': /* newmtl */ + fgets(buf, sizeof(buf), file); + sscanf(buf, "%s %s", buf, buf); + nummaterials++; + model->materials[nummaterials].name = stralloc(buf); + break; + case 'N': + fscanf(file, "%f", &model->materials[nummaterials].shininess); + /* wavefront shininess is from [0, 1000], so scale for OpenGL */ + model->materials[nummaterials].shininess /= 1000.0; + model->materials[nummaterials].shininess *= 128.0; + mat = &model->materials[nummaterials]; + break; + case 'K': + switch(buf[1]) { + case 'd': + fscanf(file, "%f %f %f", + &model->materials[nummaterials].diffuse[0], + &model->materials[nummaterials].diffuse[1], + &model->materials[nummaterials].diffuse[2]); + break; + case 's': + fscanf(file, "%f %f %f", + &model->materials[nummaterials].specular[0], + &model->materials[nummaterials].specular[1], + &model->materials[nummaterials].specular[2]); + break; + case 'a': + fscanf(file, "%f %f %f", + &model->materials[nummaterials].ambient[0], + &model->materials[nummaterials].ambient[1], + &model->materials[nummaterials].ambient[2]); + break; + default: + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + break; + } + break; + case 'd': /* alpha? */ + fscanf(file, "%f", + &model->materials[nummaterials].diffuse[3]); + break; + case 'm': /* texture map */ + fscanf(file, "%s", buf2); + /*printf("map %s\n", buf2);*/ + mat->map_kd = strdup(buf2); + break; + + default: + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + break; + } + } +} + + +/* _glmWriteMTL: write a wavefront material library file + * + * model - properly initialized GLMmodel structure + * modelpath - pathname of the model being written + * mtllibname - name of the material library to be written + */ +static void +_glmWriteMTL(GLMmodel* model, char* modelpath, char* mtllibname) +{ + FILE* file; + char* dir; + char* filename; + GLMmaterial* material; + uint i; + + dir = _glmDirName(modelpath); + filename = (char*)malloc(sizeof(char) * (strlen(dir) + strlen(mtllibname))); + strcpy(filename, dir); + strcat(filename, mtllibname); + free(dir); + + /* open the file */ + file = fopen(filename, "w"); + if (!file) { + fprintf(stderr, "_glmWriteMTL() failed: can't open file \"%s\".\n", + filename); + exit(1); + } + free(filename); + + /* spit out a header */ + fprintf(file, "# \n"); + fprintf(file, "# Wavefront MTL generated by GLM library\n"); + fprintf(file, "# \n"); + fprintf(file, "# GLM library copyright (C) 1997 by Nate Robins\n"); + fprintf(file, "# email: ndr@pobox.com\n"); + fprintf(file, "# www: http://www.pobox.com/~ndr\n"); + fprintf(file, "# \n\n"); + + for (i = 0; i < model->nummaterials; i++) { + material = &model->materials[i]; + fprintf(file, "newmtl %s\n", material->name); + fprintf(file, "Ka %f %f %f\n", + material->ambient[0], material->ambient[1], material->ambient[2]); + fprintf(file, "Kd %f %f %f\n", + material->diffuse[0], material->diffuse[1], material->diffuse[2]); + fprintf(file, "Ks %f %f %f\n", + material->specular[0],material->specular[1],material->specular[2]); + fprintf(file, "Ns %f\n", material->shininess); + fprintf(file, "\n"); + } +} + + +/* _glmFirstPass: first pass at a Wavefront OBJ file that gets all the + * statistics of the model (such as #vertices, #normals, etc) + * + * model - properly initialized GLMmodel structure + * file - (fopen'd) file descriptor + */ +static void +_glmFirstPass(GLMmodel* model, FILE* file) +{ + uint numvertices; /* number of vertices in model */ + uint numnormals; /* number of normals in model */ + uint numtexcoords; /* number of texcoords in model */ + uint numtriangles; /* number of triangles in model */ + GLMgroup* group; /* current group */ + unsigned v, n, t; + char buf[128]; + + /* make a default group */ + group = _glmAddGroup(model, "default"); + + numvertices = numnormals = numtexcoords = numtriangles = 0; + while(fscanf(file, "%s", buf) != EOF) { + switch(buf[0]) { + case '#': /* comment */ + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + break; + case 'v': /* v, vn, vt */ + switch(buf[1]) { + case '\0': /* vertex */ + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + numvertices++; + break; + case 'n': /* normal */ + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + numnormals++; + break; + case 't': /* texcoord */ + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + numtexcoords++; + break; + default: + printf("_glmFirstPass(): Unknown token \"%s\".\n", buf); + exit(1); + break; + } + break; + case 'm': + fgets(buf, sizeof(buf), file); + sscanf(buf, "%s %s", buf, buf); + model->mtllibname = stralloc(buf); + _glmReadMTL(model, buf); + break; + case 'u': + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + break; + case 'g': /* group */ + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + sscanf(buf, "%s", buf); + group = _glmAddGroup(model, buf); + break; + case 'f': /* face */ + v = n = t = 0; + fscanf(file, "%s", buf); + /* can be one of %d, %d//%d, %d/%d, %d/%d/%d %d//%d */ + if (strstr(buf, "//")) { + /* v//n */ + sscanf(buf, "%u//%u", &v, &n); + fscanf(file, "%u//%u", &v, &n); + fscanf(file, "%u//%u", &v, &n); + numtriangles++; + group->numtriangles++; + while(fscanf(file, "%u//%u", &v, &n) > 0) { + numtriangles++; + group->numtriangles++; + } + } else if (sscanf(buf, "%u/%u/%u", &v, &t, &n) == 3) { + /* v/t/n */ + fscanf(file, "%u/%u/%u", &v, &t, &n); + fscanf(file, "%u/%u/%u", &v, &t, &n); + numtriangles++; + group->numtriangles++; + while(fscanf(file, "%u/%u/%u", &v, &t, &n) > 0) { + numtriangles++; + group->numtriangles++; + } + } else if (sscanf(buf, "%u/%u", &v, &t) == 2) { + /* v/t */ + fscanf(file, "%u/%u", &v, &t); + fscanf(file, "%u/%u", &v, &t); + numtriangles++; + group->numtriangles++; + while(fscanf(file, "%u/%u", &v, &t) > 0) { + numtriangles++; + group->numtriangles++; + } + } else { + /* v */ + fscanf(file, "%u", &v); + fscanf(file, "%u", &v); + numtriangles++; + group->numtriangles++; + while(fscanf(file, "%u", &v) > 0) { + numtriangles++; + group->numtriangles++; + } + } + break; + + default: + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + break; + } + } + +#if 0 + /* announce the model statistics */ + printf(" Vertices: %d\n", numvertices); + printf(" Normals: %d\n", numnormals); + printf(" Texcoords: %d\n", numtexcoords); + printf(" Triangles: %d\n", numtriangles); + printf(" Groups: %d\n", model->numgroups); +#endif + + /* set the stats in the model structure */ + model->numvertices = numvertices; + model->numnormals = numnormals; + model->numtexcoords = numtexcoords; + model->numtriangles = numtriangles; + + /* allocate memory for the triangles in each group */ + group = model->groups; + while(group) { + group->triangles = (uint*)malloc(sizeof(uint) * group->numtriangles); + group->numtriangles = 0; + group = group->next; + } +} + +/* _glmSecondPass: second pass at a Wavefront OBJ file that gets all + * the data. + * + * model - properly initialized GLMmodel structure + * file - (fopen'd) file descriptor + */ +static void +_glmSecondPass(GLMmodel* model, FILE* file) +{ + uint numvertices; /* number of vertices in model */ + uint numnormals; /* number of normals in model */ + uint numtexcoords; /* number of texcoords in model */ + uint numtriangles; /* number of triangles in model */ + float* vertices; /* array of vertices */ + float* normals; /* array of normals */ + float* texcoords; /* array of texture coordinates */ + GLMgroup* group; /* current group pointer */ + uint material; /* current material */ + uint v, n, t; + char buf[128]; + + /* set the pointer shortcuts */ + vertices = model->vertices; + normals = model->normals; + texcoords = model->texcoords; + group = model->groups; + + /* on the second pass through the file, read all the data into the + allocated arrays */ + numvertices = numnormals = numtexcoords = 1; + numtriangles = 0; + material = 0; + while(fscanf(file, "%s", buf) != EOF) { + switch(buf[0]) { + case '#': /* comment */ + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + break; + case 'v': /* v, vn, vt */ + switch(buf[1]) { + case '\0': /* vertex */ + fscanf(file, "%f %f %f", + &vertices[3 * numvertices + X], + &vertices[3 * numvertices + Y], + &vertices[3 * numvertices + Z]); + numvertices++; + break; + case 'n': /* normal */ + fscanf(file, "%f %f %f", + &normals[3 * numnormals + X], + &normals[3 * numnormals + Y], + &normals[3 * numnormals + Z]); + numnormals++; + break; + case 't': /* texcoord */ + fscanf(file, "%f %f", + &texcoords[2 * numtexcoords + X], + &texcoords[2 * numtexcoords + Y]); + numtexcoords++; + break; + } + break; + case 'u': + fgets(buf, sizeof(buf), file); + sscanf(buf, "%s %s", buf, buf); + material = _glmFindMaterial(model, buf); + if (!group->material) + group->material = material; + /*printf("material %s = %u\n", buf, material);*/ + break; + case 'g': /* group */ + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + sscanf(buf, "%s", buf); + group = _glmFindGroup(model, buf); + group->material = material; + /*printf("GROUP %s material %u\n", buf, material);*/ + break; + case 'f': /* face */ + v = n = t = 0; + fscanf(file, "%s", buf); + /* can be one of %d, %d//%d, %d/%d, %d/%d/%d %d//%d */ + if (strstr(buf, "//")) { + /* v//n */ + sscanf(buf, "%u//%u", &v, &n); + T(numtriangles).vindices[0] = v; + T(numtriangles).nindices[0] = n; + fscanf(file, "%u//%u", &v, &n); + T(numtriangles).vindices[1] = v; + T(numtriangles).nindices[1] = n; + fscanf(file, "%u//%u", &v, &n); + T(numtriangles).vindices[2] = v; + T(numtriangles).nindices[2] = n; + group->triangles[group->numtriangles++] = numtriangles; + numtriangles++; + while(fscanf(file, "%u//%u", &v, &n) > 0) { + T(numtriangles).vindices[0] = T(numtriangles-1).vindices[0]; + T(numtriangles).nindices[0] = T(numtriangles-1).nindices[0]; + T(numtriangles).vindices[1] = T(numtriangles-1).vindices[2]; + T(numtriangles).nindices[1] = T(numtriangles-1).nindices[2]; + T(numtriangles).vindices[2] = v; + T(numtriangles).nindices[2] = n; + group->triangles[group->numtriangles++] = numtriangles; + numtriangles++; + } + } else if (sscanf(buf, "%u/%u/%u", &v, &t, &n) == 3) { + /* v/t/n */ + T(numtriangles).vindices[0] = v; + T(numtriangles).tindices[0] = t; + T(numtriangles).nindices[0] = n; + fscanf(file, "%u/%u/%u", &v, &t, &n); + T(numtriangles).vindices[1] = v; + T(numtriangles).tindices[1] = t; + T(numtriangles).nindices[1] = n; + fscanf(file, "%u/%u/%u", &v, &t, &n); + T(numtriangles).vindices[2] = v; + T(numtriangles).tindices[2] = t; + T(numtriangles).nindices[2] = n; + group->triangles[group->numtriangles++] = numtriangles; + numtriangles++; + while(fscanf(file, "%u/%u/%u", &v, &t, &n) > 0) { + T(numtriangles).vindices[0] = T(numtriangles-1).vindices[0]; + T(numtriangles).tindices[0] = T(numtriangles-1).tindices[0]; + T(numtriangles).nindices[0] = T(numtriangles-1).nindices[0]; + T(numtriangles).vindices[1] = T(numtriangles-1).vindices[2]; + T(numtriangles).tindices[1] = T(numtriangles-1).tindices[2]; + T(numtriangles).nindices[1] = T(numtriangles-1).nindices[2]; + T(numtriangles).vindices[2] = v; + T(numtriangles).tindices[2] = t; + T(numtriangles).nindices[2] = n; + group->triangles[group->numtriangles++] = numtriangles; + numtriangles++; + } + } else if (sscanf(buf, "%u/%u", &v, &t) == 2) { + /* v/t */ + T(numtriangles).vindices[0] = v; + T(numtriangles).tindices[0] = t; + fscanf(file, "%u/%u", &v, &t); + T(numtriangles).vindices[1] = v; + T(numtriangles).tindices[1] = t; + fscanf(file, "%u/%u", &v, &t); + T(numtriangles).vindices[2] = v; + T(numtriangles).tindices[2] = t; + group->triangles[group->numtriangles++] = numtriangles; + numtriangles++; + while(fscanf(file, "%u/%u", &v, &t) > 0) { + T(numtriangles).vindices[0] = T(numtriangles-1).vindices[0]; + T(numtriangles).tindices[0] = T(numtriangles-1).tindices[0]; + T(numtriangles).vindices[1] = T(numtriangles-1).vindices[2]; + T(numtriangles).tindices[1] = T(numtriangles-1).tindices[2]; + T(numtriangles).vindices[2] = v; + T(numtriangles).tindices[2] = t; + group->triangles[group->numtriangles++] = numtriangles; + numtriangles++; + } + } else { + /* v */ + sscanf(buf, "%u", &v); + T(numtriangles).vindices[0] = v; + fscanf(file, "%u", &v); + T(numtriangles).vindices[1] = v; + fscanf(file, "%u", &v); + T(numtriangles).vindices[2] = v; + group->triangles[group->numtriangles++] = numtriangles; + numtriangles++; + while(fscanf(file, "%u", &v) > 0) { + T(numtriangles).vindices[0] = T(numtriangles-1).vindices[0]; + T(numtriangles).vindices[1] = T(numtriangles-1).vindices[2]; + T(numtriangles).vindices[2] = v; + group->triangles[group->numtriangles++] = numtriangles; + numtriangles++; + } + } + break; + + default: + /* eat up rest of line */ + fgets(buf, sizeof(buf), file); + break; + } + } + +#if 0 + /* announce the memory requirements */ + printf(" Memory: %d bytes\n", + numvertices * 3*sizeof(float) + + numnormals * 3*sizeof(float) * (numnormals ? 1 : 0) + + numtexcoords * 3*sizeof(float) * (numtexcoords ? 1 : 0) + + numtriangles * sizeof(GLMtriangle)); +#endif +} + + + + +/* public functions */ + +/* glmUnitize: "unitize" a model by translating it to the origin and + * scaling it to fit in a unit cube around the origin. Returns the + * scalefactor used. + * + * model - properly initialized GLMmodel structure + */ +float +glmUnitize(GLMmodel* model) +{ + uint i; + float maxx, minx, maxy, miny, maxz, minz; + float cx, cy, cz, w, h, d; + float scale; + + assert(model); + assert(model->vertices); + + /* get the max/mins */ + maxx = minx = model->vertices[3 + X]; + maxy = miny = model->vertices[3 + Y]; + maxz = minz = model->vertices[3 + Z]; + for (i = 1; i <= model->numvertices; i++) { + if (maxx < model->vertices[3 * i + X]) + maxx = model->vertices[3 * i + X]; + if (minx > model->vertices[3 * i + X]) + minx = model->vertices[3 * i + X]; + + if (maxy < model->vertices[3 * i + Y]) + maxy = model->vertices[3 * i + Y]; + if (miny > model->vertices[3 * i + Y]) + miny = model->vertices[3 * i + Y]; + + if (maxz < model->vertices[3 * i + Z]) + maxz = model->vertices[3 * i + Z]; + if (minz > model->vertices[3 * i + Z]) + minz = model->vertices[3 * i + Z]; + } + + /* calculate model width, height, and depth */ + w = _glmAbs(maxx) + _glmAbs(minx); + h = _glmAbs(maxy) + _glmAbs(miny); + d = _glmAbs(maxz) + _glmAbs(minz); + + /* calculate center of the model */ + cx = (maxx + minx) / 2.0; + cy = (maxy + miny) / 2.0; + cz = (maxz + minz) / 2.0; + + /* calculate unitizing scale factor */ + scale = 2.0 / _glmMax(_glmMax(w, h), d); + + /* translate around center then scale */ + for (i = 1; i <= model->numvertices; i++) { + model->vertices[3 * i + X] -= cx; + model->vertices[3 * i + Y] -= cy; + model->vertices[3 * i + Z] -= cz; + model->vertices[3 * i + X] *= scale; + model->vertices[3 * i + Y] *= scale; + model->vertices[3 * i + Z] *= scale; + } + + return scale; +} + +/* glmDimensions: Calculates the dimensions (width, height, depth) of + * a model. + * + * model - initialized GLMmodel structure + * dimensions - array of 3 floats (float dimensions[3]) + */ +void +glmDimensions(GLMmodel* model, float* dimensions) +{ + uint i; + float maxx, minx, maxy, miny, maxz, minz; + + assert(model); + assert(model->vertices); + assert(dimensions); + + /* get the max/mins */ + maxx = minx = model->vertices[3 + X]; + maxy = miny = model->vertices[3 + Y]; + maxz = minz = model->vertices[3 + Z]; + for (i = 1; i <= model->numvertices; i++) { + if (maxx < model->vertices[3 * i + X]) + maxx = model->vertices[3 * i + X]; + if (minx > model->vertices[3 * i + X]) + minx = model->vertices[3 * i + X]; + + if (maxy < model->vertices[3 * i + Y]) + maxy = model->vertices[3 * i + Y]; + if (miny > model->vertices[3 * i + Y]) + miny = model->vertices[3 * i + Y]; + + if (maxz < model->vertices[3 * i + Z]) + maxz = model->vertices[3 * i + Z]; + if (minz > model->vertices[3 * i + Z]) + minz = model->vertices[3 * i + Z]; + } + + /* calculate model width, height, and depth */ + dimensions[X] = _glmAbs(maxx) + _glmAbs(minx); + dimensions[Y] = _glmAbs(maxy) + _glmAbs(miny); + dimensions[Z] = _glmAbs(maxz) + _glmAbs(minz); +} + +/* glmScale: Scales a model by a given amount. + * + * model - properly initialized GLMmodel structure + * scale - scalefactor (0.5 = half as large, 2.0 = twice as large) + */ +void +glmScale(GLMmodel* model, float scale) +{ + uint i; + + for (i = 1; i <= model->numvertices; i++) { + model->vertices[3 * i + X] *= scale; + model->vertices[3 * i + Y] *= scale; + model->vertices[3 * i + Z] *= scale; + } +} + +/* glmReverseWinding: Reverse the polygon winding for all polygons in + * this model. Default winding is counter-clockwise. Also changes + * the direction of the normals. + * + * model - properly initialized GLMmodel structure + */ +void +glmReverseWinding(GLMmodel* model) +{ + uint i, swap; + + assert(model); + + for (i = 0; i < model->numtriangles; i++) { + swap = T(i).vindices[0]; + T(i).vindices[0] = T(i).vindices[2]; + T(i).vindices[2] = swap; + + if (model->numnormals) { + swap = T(i).nindices[0]; + T(i).nindices[0] = T(i).nindices[2]; + T(i).nindices[2] = swap; + } + + if (model->numtexcoords) { + swap = T(i).tindices[0]; + T(i).tindices[0] = T(i).tindices[2]; + T(i).tindices[2] = swap; + } + } + + /* reverse facet normals */ + for (i = 1; i <= model->numfacetnorms; i++) { + model->facetnorms[3 * i + X] = -model->facetnorms[3 * i + X]; + model->facetnorms[3 * i + Y] = -model->facetnorms[3 * i + Y]; + model->facetnorms[3 * i + Z] = -model->facetnorms[3 * i + Z]; + } + + /* reverse vertex normals */ + for (i = 1; i <= model->numnormals; i++) { + model->normals[3 * i + X] = -model->normals[3 * i + X]; + model->normals[3 * i + Y] = -model->normals[3 * i + Y]; + model->normals[3 * i + Z] = -model->normals[3 * i + Z]; + } +} + +/* glmFacetNormals: Generates facet normals for a model (by taking the + * cross product of the two vectors derived from the sides of each + * triangle). Assumes a counter-clockwise winding. + * + * model - initialized GLMmodel structure + */ +void +glmFacetNormals(GLMmodel* model) +{ + uint i; + float u[3]; + float v[3]; + + assert(model); + assert(model->vertices); + + /* clobber any old facetnormals */ + if (model->facetnorms) + free(model->facetnorms); + + /* allocate memory for the new facet normals */ + model->numfacetnorms = model->numtriangles; + model->facetnorms = (float*)malloc(sizeof(float) * + 3 * (model->numfacetnorms + 1)); + + for (i = 0; i < model->numtriangles; i++) { + model->triangles[i].findex = i+1; + + u[X] = model->vertices[3 * T(i).vindices[1] + X] - + model->vertices[3 * T(i).vindices[0] + X]; + u[Y] = model->vertices[3 * T(i).vindices[1] + Y] - + model->vertices[3 * T(i).vindices[0] + Y]; + u[Z] = model->vertices[3 * T(i).vindices[1] + Z] - + model->vertices[3 * T(i).vindices[0] + Z]; + + v[X] = model->vertices[3 * T(i).vindices[2] + X] - + model->vertices[3 * T(i).vindices[0] + X]; + v[Y] = model->vertices[3 * T(i).vindices[2] + Y] - + model->vertices[3 * T(i).vindices[0] + Y]; + v[Z] = model->vertices[3 * T(i).vindices[2] + Z] - + model->vertices[3 * T(i).vindices[0] + Z]; + + _glmCross(u, v, &model->facetnorms[3 * (i+1)]); + _glmNormalize(&model->facetnorms[3 * (i+1)]); + } +} + +/* glmVertexNormals: Generates smooth vertex normals for a model. + * First builds a list of all the triangles each vertex is in. Then + * loops through each vertex in the the list averaging all the facet + * normals of the triangles each vertex is in. Finally, sets the + * normal index in the triangle for the vertex to the generated smooth + * normal. If the dot product of a facet normal and the facet normal + * associated with the first triangle in the list of triangles the + * current vertex is in is greater than the cosine of the angle + * parameter to the function, that facet normal is not added into the + * average normal calculation and the corresponding vertex is given + * the facet normal. This tends to preserve hard edges. The angle to + * use depends on the model, but 90 degrees is usually a good start. + * + * model - initialized GLMmodel structure + * angle - maximum angle (in degrees) to smooth across + */ +void +glmVertexNormals(GLMmodel* model, float angle) +{ + GLMnode* node; + GLMnode* tail; + GLMnode** members; + float* normals; + uint numnormals; + float average[3]; + float dot, cos_angle; + uint i, avg; + + assert(model); + assert(model->facetnorms); + + /* calculate the cosine of the angle (in degrees) */ + cos_angle = cos(angle * M_PI / 180.0); + + /* nuke any previous normals */ + if (model->normals) + free(model->normals); + + /* allocate space for new normals */ + model->numnormals = model->numtriangles * 3; /* 3 normals per triangle */ + model->normals = (float*)malloc(sizeof(float)* 3* (model->numnormals+1)); + + /* allocate a structure that will hold a linked list of triangle + indices for each vertex */ + members = (GLMnode**)malloc(sizeof(GLMnode*) * (model->numvertices + 1)); + for (i = 1; i <= model->numvertices; i++) + members[i] = NULL; + + /* for every triangle, create a node for each vertex in it */ + for (i = 0; i < model->numtriangles; i++) { + node = (GLMnode*)malloc(sizeof(GLMnode)); + node->index = i; + node->next = members[T(i).vindices[0]]; + members[T(i).vindices[0]] = node; + + node = (GLMnode*)malloc(sizeof(GLMnode)); + node->index = i; + node->next = members[T(i).vindices[1]]; + members[T(i).vindices[1]] = node; + + node = (GLMnode*)malloc(sizeof(GLMnode)); + node->index = i; + node->next = members[T(i).vindices[2]]; + members[T(i).vindices[2]] = node; + } + + /* calculate the average normal for each vertex */ + numnormals = 1; + for (i = 1; i <= model->numvertices; i++) { + /* calculate an average normal for this vertex by averaging the + facet normal of every triangle this vertex is in */ + node = members[i]; + if (!node) + fprintf(stderr, "glmVertexNormals(): vertex w/o a triangle\n"); + average[0] = 0.0; average[1] = 0.0; average[2] = 0.0; + avg = 0; + while (node) { + /* only average if the dot product of the angle between the two + facet normals is greater than the cosine of the threshold + angle -- or, said another way, the angle between the two + facet normals is less than (or equal to) the threshold angle */ + dot = _glmDot(&model->facetnorms[3 * T(node->index).findex], + &model->facetnorms[3 * T(members[i]->index).findex]); + if (dot > cos_angle) { + node->averaged = TRUE; + average[0] += model->facetnorms[3 * T(node->index).findex + 0]; + average[1] += model->facetnorms[3 * T(node->index).findex + 1]; + average[2] += model->facetnorms[3 * T(node->index).findex + 2]; + avg = 1; /* we averaged at least one normal! */ + } else { + node->averaged = FALSE; + } + node = node->next; + } + + if (avg) { + /* normalize the averaged normal */ + _glmNormalize(average); + + /* add the normal to the vertex normals list */ + model->normals[3 * numnormals + 0] = average[0]; + model->normals[3 * numnormals + 1] = average[1]; + model->normals[3 * numnormals + 2] = average[2]; + avg = numnormals; + numnormals++; + } + + /* set the normal of this vertex in each triangle it is in */ + node = members[i]; + while (node) { + if (node->averaged) { + /* if this node was averaged, use the average normal */ + if (T(node->index).vindices[0] == i) + T(node->index).nindices[0] = avg; + else if (T(node->index).vindices[1] == i) + T(node->index).nindices[1] = avg; + else if (T(node->index).vindices[2] == i) + T(node->index).nindices[2] = avg; + } else { + /* if this node wasn't averaged, use the facet normal */ + model->normals[3 * numnormals + 0] = + model->facetnorms[3 * T(node->index).findex + 0]; + model->normals[3 * numnormals + 1] = + model->facetnorms[3 * T(node->index).findex + 1]; + model->normals[3 * numnormals + 2] = + model->facetnorms[3 * T(node->index).findex + 2]; + if (T(node->index).vindices[0] == i) + T(node->index).nindices[0] = numnormals; + else if (T(node->index).vindices[1] == i) + T(node->index).nindices[1] = numnormals; + else if (T(node->index).vindices[2] == i) + T(node->index).nindices[2] = numnormals; + numnormals++; + } + node = node->next; + } + } + + model->numnormals = numnormals - 1; + + /* free the member information */ + for (i = 1; i <= model->numvertices; i++) { + node = members[i]; + while (node) { + tail = node; + node = node->next; + free(tail); + } + } + free(members); + + /* pack the normals array (we previously allocated the maximum + number of normals that could possibly be created (numtriangles * + 3), so get rid of some of them (usually alot unless none of the + facet normals were averaged)) */ + normals = model->normals; + model->normals = (float*)malloc(sizeof(float)* 3* (model->numnormals+1)); + for (i = 1; i <= model->numnormals; i++) { + model->normals[3 * i + 0] = normals[3 * i + 0]; + model->normals[3 * i + 1] = normals[3 * i + 1]; + model->normals[3 * i + 2] = normals[3 * i + 2]; + } + free(normals); + + printf("glmVertexNormals(): %d normals generated\n", model->numnormals); +} + + +/* glmLinearTexture: Generates texture coordinates according to a + * linear projection of the texture map. It generates these by + * linearly mapping the vertices onto a square. + * + * model - pointer to initialized GLMmodel structure + */ +void +glmLinearTexture(GLMmodel* model) +{ + GLMgroup *group; + float dimensions[3]; + float x, y, scalefactor; + uint i; + + assert(model); + + if (model->texcoords) + free(model->texcoords); + model->numtexcoords = model->numvertices; + model->texcoords=(float*)malloc(sizeof(float)*2*(model->numtexcoords+1)); + + glmDimensions(model, dimensions); + scalefactor = 2.0 / + _glmAbs(_glmMax(_glmMax(dimensions[0], dimensions[1]), dimensions[2])); + + /* do the calculations */ + for(i = 1; i <= model->numvertices; i++) { + x = model->vertices[3 * i + 0] * scalefactor; + y = model->vertices[3 * i + 2] * scalefactor; + model->texcoords[2 * i + 0] = (x + 1.0) / 2.0; + model->texcoords[2 * i + 1] = (y + 1.0) / 2.0; + } + + /* go through and put texture coordinate indices in all the triangles */ + group = model->groups; + while(group) { + for(i = 0; i < group->numtriangles; i++) { + T(group->triangles[i]).tindices[0] = T(group->triangles[i]).vindices[0]; + T(group->triangles[i]).tindices[1] = T(group->triangles[i]).vindices[1]; + T(group->triangles[i]).tindices[2] = T(group->triangles[i]).vindices[2]; + } + group = group->next; + } + +#if 0 + printf("glmLinearTexture(): generated %d linear texture coordinates\n", + model->numtexcoords); +#endif +} + +/* glmSpheremapTexture: Generates texture coordinates according to a + * spherical projection of the texture map. Sometimes referred to as + * spheremap, or reflection map texture coordinates. It generates + * these by using the normal to calculate where that vertex would map + * onto a sphere. Since it is impossible to map something flat + * perfectly onto something spherical, there is distortion at the + * poles. This particular implementation causes the poles along the X + * axis to be distorted. + * + * model - pointer to initialized GLMmodel structure + */ +void +glmSpheremapTexture(GLMmodel* model) +{ + GLMgroup* group; + float theta, phi, rho, x, y, z, r; + uint i; + + assert(model); + assert(model->normals); + + if (model->texcoords) + free(model->texcoords); + model->numtexcoords = model->numnormals; + model->texcoords=(float*)malloc(sizeof(float)*2*(model->numtexcoords+1)); + + /* do the calculations */ + for (i = 1; i <= model->numnormals; i++) { + z = model->normals[3 * i + 0]; /* re-arrange for pole distortion */ + y = model->normals[3 * i + 1]; + x = model->normals[3 * i + 2]; + r = sqrt((x * x) + (y * y)); + rho = sqrt((r * r) + (z * z)); + + if(r == 0.0) { + theta = 0.0; + phi = 0.0; + } else { + if(z == 0.0) + phi = M_PI / 2.0; + else + phi = acos(z / rho); + +#if WE_DONT_NEED_THIS_CODE + if(x == 0.0) + theta = M_PI / 2.0; /* asin(y / r); */ + else + theta = acos(x / r); +#endif + + if(y == 0.0) + theta = M_PI / 2.0; /* acos(x / r); */ + else + theta = asin(y / r) + (M_PI / 2.0); + } + + model->texcoords[2 * i + 0] = theta / M_PI; + model->texcoords[2 * i + 1] = phi / M_PI; + } + + /* go through and put texcoord indices in all the triangles */ + group = model->groups; + while(group) { + for (i = 0; i < group->numtriangles; i++) { + T(group->triangles[i]).tindices[0] = T(group->triangles[i]).nindices[0]; + T(group->triangles[i]).tindices[1] = T(group->triangles[i]).nindices[1]; + T(group->triangles[i]).tindices[2] = T(group->triangles[i]).nindices[2]; + } + group = group->next; + } + +#if 0 + printf("glmSpheremapTexture(): generated %d spheremap texture coordinates\n", + model->numtexcoords); +#endif +} + +/* glmDelete: Deletes a GLMmodel structure. + * + * model - initialized GLMmodel structure + */ +void +glmDelete(GLMmodel* model) +{ + GLMgroup* group; + uint i; + + assert(model); + + if (model->pathname) free(model->pathname); + if (model->mtllibname) free(model->mtllibname); + if (model->vertices) free(model->vertices); + if (model->normals) free(model->normals); + if (model->texcoords) free(model->texcoords); + if (model->facetnorms) free(model->facetnorms); + if (model->triangles) free(model->triangles); + if (model->materials) { + for (i = 0; i < model->nummaterials; i++) + free(model->materials[i].name); + } + free(model->materials); + while(model->groups) { + group = model->groups; + model->groups = model->groups->next; + free(group->name); + free(group->triangles); + free(group); + } + + free(model); +} + +static GLMmaterial * +glmDefaultMaterial(void) +{ + GLMmaterial *m = (GLMmaterial *) calloc(1, sizeof(GLMmaterial)); + + m->diffuse[0] = 0.75; + m->diffuse[1] = 0.75; + m->diffuse[2] = 0.75; + m->diffuse[3] = 1.0; + + m->specular[0] = 1.0; + m->specular[1] = 1.0; + m->specular[2] = 1.0; + m->specular[3] = 1.0; + + m->shininess = 5; + + return m; +} + + +/* glmReadOBJ: Reads a model description from a Wavefront .OBJ file. + * Returns a pointer to the created object which should be free'd with + * glmDelete(). + * + * filename - name of the file containing the Wavefront .OBJ format data. + */ +GLMmodel* +glmReadOBJ(char* filename) +{ + GLMmodel* model; + FILE* file; + + /* open the file */ + file = fopen(filename, "r"); + if (!file) { + fprintf(stderr, "glmReadOBJ() failed: can't open data file \"%s\".\n", + filename); + exit(1); + } + +#if 0 + /* announce the model name */ + printf("Model: %s\n", filename); +#endif + + /* allocate a new model */ + model = (GLMmodel*)malloc(sizeof(GLMmodel)); + model->pathname = stralloc(filename); + model->mtllibname = NULL; + model->numvertices = 0; + model->vertices = NULL; + model->numnormals = 0; + model->normals = NULL; + model->numtexcoords = 0; + model->texcoords = NULL; + model->numfacetnorms = 0; + model->facetnorms = NULL; + model->numtriangles = 0; + model->triangles = NULL; + model->nummaterials = 0; + model->materials = NULL; + model->numgroups = 0; + model->groups = NULL; + model->position[0] = 0.0; + model->position[1] = 0.0; + model->position[2] = 0.0; + model->scale = 1.0; + + /* make a first pass through the file to get a count of the number + of vertices, normals, texcoords & triangles */ + _glmFirstPass(model, file); + + /* allocate memory */ + model->vertices = (float*)malloc(sizeof(float) * + 3 * (model->numvertices + 1)); + model->triangles = (GLMtriangle*)malloc(sizeof(GLMtriangle) * + model->numtriangles); + if (model->numnormals) { + model->normals = (float*)malloc(sizeof(float) * + 3 * (model->numnormals + 1)); + } + if (model->numtexcoords) { + model->texcoords = (float*)malloc(sizeof(float) * + 2 * (model->numtexcoords + 1)); + } + + /* rewind to beginning of file and read in the data this pass */ + rewind(file); + + _glmSecondPass(model, file); + + /* close the file */ + fclose(file); + + if (!model->materials) { + model->materials = glmDefaultMaterial(); + model->nummaterials = 1; + } + + return model; +} + +/* glmWriteOBJ: Writes a model description in Wavefront .OBJ format to + * a file. + * + * model - initialized GLMmodel structure + * filename - name of the file to write the Wavefront .OBJ format data to + * mode - a bitwise or of values describing what is written to the file + * GLM_NONE - render with only vertices + * GLM_FLAT - render with facet normals + * GLM_SMOOTH - render with vertex normals + * GLM_TEXTURE - render with texture coords + * GLM_COLOR - render with colors (color material) + * GLM_MATERIAL - render with materials + * GLM_COLOR and GLM_MATERIAL should not both be specified. + * GLM_FLAT and GLM_SMOOTH should not both be specified. + */ +void +glmWriteOBJ(GLMmodel* model, char* filename, uint mode) +{ + uint i; + FILE* file; + GLMgroup* group; + + assert(model); + + /* do a bit of warning */ + if (mode & GLM_FLAT && !model->facetnorms) { + printf("glmWriteOBJ() warning: flat normal output requested " + "with no facet normals defined.\n"); + mode &= ~GLM_FLAT; + } + if (mode & GLM_SMOOTH && !model->normals) { + printf("glmWriteOBJ() warning: smooth normal output requested " + "with no normals defined.\n"); + mode &= ~GLM_SMOOTH; + } + if (mode & GLM_TEXTURE && !model->texcoords) { + printf("glmWriteOBJ() warning: texture coordinate output requested " + "with no texture coordinates defined.\n"); + mode &= ~GLM_TEXTURE; + } + if (mode & GLM_FLAT && mode & GLM_SMOOTH) { + printf("glmWriteOBJ() warning: flat normal output requested " + "and smooth normal output requested (using smooth).\n"); + mode &= ~GLM_FLAT; + } + + /* open the file */ + file = fopen(filename, "w"); + if (!file) { + fprintf(stderr, "glmWriteOBJ() failed: can't open file \"%s\" to write.\n", + filename); + exit(1); + } + + /* spit out a header */ + fprintf(file, "# \n"); + fprintf(file, "# Wavefront OBJ generated by GLM library\n"); + fprintf(file, "# \n"); + fprintf(file, "# GLM library copyright (C) 1997 by Nate Robins\n"); + fprintf(file, "# email: ndr@pobox.com\n"); + fprintf(file, "# www: http://www.pobox.com/~ndr\n"); + fprintf(file, "# \n"); + + if (mode & GLM_MATERIAL && model->mtllibname) { + fprintf(file, "\nmtllib %s\n\n", model->mtllibname); + _glmWriteMTL(model, filename, model->mtllibname); + } + + /* spit out the vertices */ + fprintf(file, "\n"); + fprintf(file, "# %d vertices\n", model->numvertices); + for (i = 1; i <= model->numvertices; i++) { + fprintf(file, "v %f %f %f\n", + model->vertices[3 * i + 0], + model->vertices[3 * i + 1], + model->vertices[3 * i + 2]); + } + + /* spit out the smooth/flat normals */ + if (mode & GLM_SMOOTH) { + fprintf(file, "\n"); + fprintf(file, "# %d normals\n", model->numnormals); + for (i = 1; i <= model->numnormals; i++) { + fprintf(file, "vn %f %f %f\n", + model->normals[3 * i + 0], + model->normals[3 * i + 1], + model->normals[3 * i + 2]); + } + } else if (mode & GLM_FLAT) { + fprintf(file, "\n"); + fprintf(file, "# %d normals\n", model->numfacetnorms); + for (i = 1; i <= model->numnormals; i++) { + fprintf(file, "vn %f %f %f\n", + model->facetnorms[3 * i + 0], + model->facetnorms[3 * i + 1], + model->facetnorms[3 * i + 2]); + } + } + + /* spit out the texture coordinates */ + if (mode & GLM_TEXTURE) { + fprintf(file, "\n"); + fprintf(file, "# %d texcoords\n", model->numtexcoords); + for (i = 1; i <= model->numtexcoords; i++) { + fprintf(file, "vt %f %f\n", + model->texcoords[2 * i + 0], + model->texcoords[2 * i + 1]); + } + } + + fprintf(file, "\n"); + fprintf(file, "# %d groups\n", model->numgroups); + fprintf(file, "# %d faces (triangles)\n", model->numtriangles); + fprintf(file, "\n"); + + group = model->groups; + while(group) { + fprintf(file, "g %s\n", group->name); + if (mode & GLM_MATERIAL) + fprintf(file, "usemtl %s\n", model->materials[group->material].name); + for (i = 0; i < group->numtriangles; i++) { + if (mode & GLM_SMOOTH && mode & GLM_TEXTURE) { + fprintf(file, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", + T(group->triangles[i]).vindices[0], + T(group->triangles[i]).nindices[0], + T(group->triangles[i]).tindices[0], + T(group->triangles[i]).vindices[1], + T(group->triangles[i]).nindices[1], + T(group->triangles[i]).tindices[1], + T(group->triangles[i]).vindices[2], + T(group->triangles[i]).nindices[2], + T(group->triangles[i]).tindices[2]); + } else if (mode & GLM_FLAT && mode & GLM_TEXTURE) { + fprintf(file, "f %d/%d %d/%d %d/%d\n", + T(group->triangles[i]).vindices[0], + T(group->triangles[i]).findex, + T(group->triangles[i]).vindices[1], + T(group->triangles[i]).findex, + T(group->triangles[i]).vindices[2], + T(group->triangles[i]).findex); + } else if (mode & GLM_TEXTURE) { + fprintf(file, "f %d/%d %d/%d %d/%d\n", + T(group->triangles[i]).vindices[0], + T(group->triangles[i]).tindices[0], + T(group->triangles[i]).vindices[1], + T(group->triangles[i]).tindices[1], + T(group->triangles[i]).vindices[2], + T(group->triangles[i]).tindices[2]); + } else if (mode & GLM_SMOOTH) { + fprintf(file, "f %d//%d %d//%d %d//%d\n", + T(group->triangles[i]).vindices[0], + T(group->triangles[i]).nindices[0], + T(group->triangles[i]).vindices[1], + T(group->triangles[i]).nindices[1], + T(group->triangles[i]).vindices[2], + T(group->triangles[i]).nindices[2]); + } else if (mode & GLM_FLAT) { + fprintf(file, "f %d//%d %d//%d %d//%d\n", + T(group->triangles[i]).vindices[0], + T(group->triangles[i]).findex, + T(group->triangles[i]).vindices[1], + T(group->triangles[i]).findex, + T(group->triangles[i]).vindices[2], + T(group->triangles[i]).findex); + } else { + fprintf(file, "f %d %d %d\n", + T(group->triangles[i]).vindices[0], + T(group->triangles[i]).vindices[1], + T(group->triangles[i]).vindices[2]); + } + } + fprintf(file, "\n"); + group = group->next; + } + + fclose(file); +} + +/* glmWeld: eliminate (weld) vectors that are within an epsilon of + * each other. + * + * model - initialized GLMmodel structure + * epsilon - maximum difference between vertices + * ( 0.00001 is a good start for a unitized model) + * + */ +void +glmWeld(GLMmodel* model, float epsilon) +{ + float* vectors; + float* copies; + uint numvectors; + uint i; + + /* vertices */ + numvectors = model->numvertices; + vectors = model->vertices; + copies = _glmWeldVectors(vectors, &numvectors, epsilon); + + printf("glmWeld(): %d redundant vertices.\n", + model->numvertices - numvectors - 1); + + for (i = 0; i < model->numtriangles; i++) { + T(i).vindices[0] = (uint)vectors[3 * T(i).vindices[0] + 0]; + T(i).vindices[1] = (uint)vectors[3 * T(i).vindices[1] + 0]; + T(i).vindices[2] = (uint)vectors[3 * T(i).vindices[2] + 0]; + } + + /* free space for old vertices */ + free(vectors); + + /* allocate space for the new vertices */ + model->numvertices = numvectors; + model->vertices = (float*)malloc(sizeof(float) * + 3 * (model->numvertices + 1)); + + /* copy the optimized vertices into the actual vertex list */ + for (i = 1; i <= model->numvertices; i++) { + model->vertices[3 * i + 0] = copies[3 * i + 0]; + model->vertices[3 * i + 1] = copies[3 * i + 1]; + model->vertices[3 * i + 2] = copies[3 * i + 2]; + } + + free(copies); +} + + +void +glmReIndex(GLMmodel *model) +{ + uint i, j, n; + GLMgroup* group; + float *newNormals = NULL; + float *newTexcoords = NULL; + const uint numv = model->numvertices; + + if (model->numnormals > 0) + newNormals = (float *) malloc((numv + 1) * 3 * sizeof(float)); + + if (model->numtexcoords > 0) + newTexcoords = (float *) malloc((numv + 1) * 2 * sizeof(float)); + + for (group = model->groups; group; group = group->next) { + + n = group->numtriangles; + + group->triIndexes = (uint *) malloc(n * 3 * sizeof(uint)); + + group->minIndex = 10000000; + group->maxIndex = 0; + + for (i = 0; i < n; i++) { + + for (j = 0; j < 3; j++) { + uint vindex = T(group->triangles[i]).vindices[j]; + uint nindex = T(group->triangles[i]).nindices[j]; + uint tindex = T(group->triangles[i]).tindices[j]; + + float *nrm = &model->normals[nindex * 3]; + float *tex = &model->texcoords[tindex * 2]; + + if (newNormals) { + assert(vindex * 3 + 2 < (numv + 1) * 3); + newNormals[vindex * 3 + 0] = nrm[0]; + newNormals[vindex * 3 + 1] = nrm[1]; + newNormals[vindex * 3 + 2] = nrm[2]; + } + if (newTexcoords) { + newTexcoords[vindex * 2 + 0] = tex[0]; + newTexcoords[vindex * 2 + 1] = tex[1]; + } + + T(group->triangles[i]).nindices[j] = vindex; + T(group->triangles[i]).tindices[j] = vindex; + + group->triIndexes[i * 3 + j] = vindex; + + if (vindex > group->maxIndex) + group->maxIndex = vindex; + if (vindex < group->minIndex) + group->minIndex = vindex; + } + } + } + + if (newNormals) { + free(model->normals); + model->normals = newNormals; + model->numnormals = model->numvertices; + } + + if (newTexcoords) { + free(model->texcoords); + model->texcoords = newTexcoords; + model->numtexcoords = model->numvertices; + } +} + + + +void +glmPrint(const GLMmodel *model) +{ + uint i, j, grp, n; + GLMgroup* group; + uint totalTris = 0; + + grp = 0; + + printf("%u vertices\n", model->numvertices); + printf("%u normals\n", model->numnormals); + printf("%u texcoords\n", model->numtexcoords); + + for (group = model->groups; group; group = group->next, grp++) { + printf("Group %u:\n", grp); + printf(" Min index %u, max index %u\n", group->minIndex, group->maxIndex); + +#if 0 + if (mode & GLM_MATERIAL) { + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, + model->materials[group->material].ambient); + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, + model->materials[group->material].diffuse); + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, + model->materials[group->material].specular); + glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, + model->materials[group->material].shininess); + } + + if (mode & GLM_COLOR) { + glColor3fv(model->materials[group->material].diffuse); + } +#endif + totalTris += group->numtriangles; + + printf(" %u triangles\n", group->numtriangles); + n = group->numtriangles; + if (n > 10) + n = 10; + + for (i = 0; i < n; i++) { + + printf(" %u: vert ", i); + for (j = 0; j < 3; j++) { + printf("%u ", T(group->triangles[i]).vindices[j]); + } + + printf(" normal "); + for (j = 0; j < 3; j++) { + printf("%u ", T(group->triangles[i]).nindices[j]); + } + + printf(" tex "); + for (j = 0; j < 3; j++) { + printf("%u ", T(group->triangles[i]).tindices[j]); + } + + printf("\n"); + } + } + printf("Total tris: %u\n", totalTris); +} + + + +#if 0 + /* normals */ + if (model->numnormals) { + numvectors = model->numnormals; + vectors = model->normals; + copies = _glmOptimizeVectors(vectors, &numvectors); + + printf("glmOptimize(): %d redundant normals.\n", + model->numnormals - numvectors); + + for (i = 0; i < model->numtriangles; i++) { + T(i).nindices[0] = (uint)vectors[3 * T(i).nindices[0] + 0]; + T(i).nindices[1] = (uint)vectors[3 * T(i).nindices[1] + 0]; + T(i).nindices[2] = (uint)vectors[3 * T(i).nindices[2] + 0]; + } + + /* free space for old normals */ + free(vectors); + + /* allocate space for the new normals */ + model->numnormals = numvectors; + model->normals = (float*)malloc(sizeof(float) * + 3 * (model->numnormals + 1)); + + /* copy the optimized vertices into the actual vertex list */ + for (i = 1; i <= model->numnormals; i++) { + model->normals[3 * i + 0] = copies[3 * i + 0]; + model->normals[3 * i + 1] = copies[3 * i + 1]; + model->normals[3 * i + 2] = copies[3 * i + 2]; + } + + free(copies); + } + + /* texcoords */ + if (model->numtexcoords) { + numvectors = model->numtexcoords; + vectors = model->texcoords; + copies = _glmOptimizeVectors(vectors, &numvectors); + + printf("glmOptimize(): %d redundant texcoords.\n", + model->numtexcoords - numvectors); + + for (i = 0; i < model->numtriangles; i++) { + for (j = 0; j < 3; j++) { + T(i).tindices[j] = (uint)vectors[3 * T(i).tindices[j] + 0]; + } + } + + /* free space for old texcoords */ + free(vectors); + + /* allocate space for the new texcoords */ + model->numtexcoords = numvectors; + model->texcoords = (float*)malloc(sizeof(float) * + 2 * (model->numtexcoords + 1)); + + /* copy the optimized vertices into the actual vertex list */ + for (i = 1; i <= model->numtexcoords; i++) { + model->texcoords[2 * i + 0] = copies[2 * i + 0]; + model->texcoords[2 * i + 1] = copies[2 * i + 1]; + } + + free(copies); + } +#endif + +#if 0 + /* look for unused vertices */ + /* look for unused normals */ + /* look for unused texcoords */ + for (i = 1; i <= model->numvertices; i++) { + for (j = 0; j < model->numtriangles; i++) { + if (T(j).vindices[0] == i || + T(j).vindices[1] == i || + T(j).vindices[1] == i) + break; + } + } +#endif -- cgit v1.2.3