/* obj.c * * Kamil Kaminski * kamilkss@gmail.com * * OBJ Loader * initial code: http://www.gamedev.net/community/forums/topic.asp?topic_id=312335 * * notes: the mtl file should be implemented using arrays * more that one mtl needs to be supported some day * only works for *quads* now * */ #include #include #include #include #include "obj.h" char ObjLastFname[101]; ObjModel* ObjLoadModel(char *memory, size_t size) { /* if size is 0, meaning file was not loaded correctly, return null */ if (!size) return NULL; ObjModel *ret = (ObjModel *) malloc(sizeof(ObjModel)); if (!ret) { fprintf(stderr, "ObjLoadModel() failed, %s:%d\n", __FILE__, __LINE__); perror("malloc"); exit(-1); } /* initialize to zero */ memset(ret, 0, sizeof(ObjModel)); char *p = NULL, *e = NULL; unsigned int line = 1; p = memory; e = memory + size; /* count the number of normals, texcoords, vertices, and faces, line by line */ while (p != e) { /* skip commented lines */ if (memcmp(p, "#", 1) == 0) ; else if (memcmp(p, "vn", 2) == 0) ret->nNormal++; else if (memcmp(p, "vt", 2) == 0) ret->nTexCoord++; else if (memcmp(p, "v", 1) == 0) ret->nVertex++; else if (memcmp(p, "f", 1) == 0) ret->nFace++; /* seek to new line */ while (*p++ != (char) 0x0a) ; } /* allocate memory for arrays */ ret->VertexArray = (ObjVertex *) malloc(sizeof(ObjVertex) * ret->nVertex); ret->NormalArray = (ObjNormal *) malloc(sizeof(ObjNormal) * ret->nNormal); ret->TexCoordArray = (ObjTexCoord *) malloc(sizeof(ObjTexCoord) * ret->nTexCoord); ret->FaceArray = (ObjFace *) malloc(sizeof(ObjFace) * ret->nFace); ret->mtllib = (char *) malloc(sizeof(char) * 31); ret->objectName = (char *) malloc(sizeof(char) * 31); ret->usemtl = (char *) malloc(sizeof(char) * 31); *ret->mtllib = '\0'; *ret->objectName = '\0'; *ret->usemtl = '\0'; if (!ret->VertexArray || !ret->NormalArray || !ret->TexCoordArray || !ret->FaceArray || !ret->mtllib || !ret->objectName || !ret->usemtl) { fprintf(stderr, "ObjLoadModel() failed, %s:%d\n", __FILE__, __LINE__); perror("malloc"); exit(-1); } p = memory; unsigned int nV = 0, nN = 0, nT = 0, nF = 0; unsigned int nread = 0; while (p != e) { /* skip commented lines */ if (memcmp(p, "#", 1) == 0) ; /* check for mtl file */ else if (memcmp(p, "mtllib", 6) == 0) sscanf(p, "mtllib %s", ret->mtllib); /* valgrind reports this as false positive */ /* check if mtl file will be used */ else if (memcmp(p, "usemtl", 6) == 0) sscanf(p, "usemtl %s", ret->usemtl); else if (memcmp(p, "g", 1) == 0) sscanf(p, "g %s", ret->objectName); /* parse a normal */ else if (memcmp(p, "vn", 2) == 0) { nread = sscanf(p, "vn %f %f %f", &ret->NormalArray[nN].x, &ret->NormalArray[nN].y, &ret->NormalArray[nN].z); if (nread != 3) printf("vn: read only %d instead of 3\n", nread); nN++; } /* parse a texture coordinates */ else if (memcmp(p, "vt", 2) == 0) { nread = sscanf(p, "vt %f %f", &ret->TexCoordArray[nT].s, &ret->TexCoordArray[nT].t); if (nread != 2) printf("vt: read only %d instead of 2\n", nread); ret->TexCoordArray[nT].r = 0.0f; ret->TexCoordArray[nT].q = 1.0f; nT++; } /* parse a vertex */ else if (memcmp(p, "v", 1) == 0) /* or *p == 'v' */ { nread = sscanf(p, "v %f %f %f", &ret->VertexArray[nV].x, &ret->VertexArray[nV].y, &ret->VertexArray[nV].z); if (nread != 3) printf("v: read only %d instead of 3\n", nread); ret->VertexArray[nV].w = 1.0f; nV++; } /* quad */ else if (memcmp(p, "f", 1) == 0) /* or *p == 'f' */ { nread = sscanf(p, "f %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d", &ret->FaceArray[nF].Vertex[0], &ret->FaceArray[nF].TexCoord[0], &ret->FaceArray[nF].Normal[0], &ret->FaceArray[nF].Vertex[1], &ret->FaceArray[nF].TexCoord[1], &ret->FaceArray[nF].Normal[1], &ret->FaceArray[nF].Vertex[2], &ret->FaceArray[nF].TexCoord[2], &ret->FaceArray[nF].Normal[2], &ret->FaceArray[nF].Vertex[3], &ret->FaceArray[nF].TexCoord[3], &ret->FaceArray[nF].Normal[3]); /* obj file counts from 1, I want to count from 0 */ ret->FaceArray[nF].Vertex[0]--, ret->FaceArray[nF].TexCoord[0]--, ret->FaceArray[nF].Normal[0]--, ret->FaceArray[nF].Vertex[1]--, ret->FaceArray[nF].TexCoord[1]--, ret->FaceArray[nF].Normal[1]--, ret->FaceArray[nF].Vertex[2]--, ret->FaceArray[nF].TexCoord[2]--, ret->FaceArray[nF].Normal[2]--, ret->FaceArray[nF].Vertex[3]--, ret->FaceArray[nF].TexCoord[3]--, ret->FaceArray[nF].Normal[3]--; if (nread != 12) printf("f: read only %d instead of 12\n", nread); nF++; } /* seek to a newline */ while (*p++ != (char) 0x0a) ; line++; } /* sanity check */ if ((ret->nVertex != nV) || (ret->nNormal != nN) || (ret->nTexCoord != nT) || (ret->nFace != nF)) { fprintf(stdout, "ObjLoadModel(): warning, the number of scanned items does not equal to number of read\n"); if (ret->nVertex != nV) fprintf(stdout, "vertices: scanned %d, read %d\n", ret->nVertex, nV); if (ret->nNormal != nN) fprintf(stdout, "normals: scanned %d, read %d\n", ret->nNormal, nN); if (ret->nTexCoord != nT) fprintf(stdout, "texcoords: scanned %d, read %d\n", ret->nTexCoord, nT); if (ret->nFace != nF) fprintf(stdout, "faces: scanned %d, read %d\n", ret->nFace, nF); } /* load the mtl file */ if (ret->mtllib != NULL && ret->usemtl) { /* append directory to filename */ /* sprintf(fname, "./cube/%s", ret->mtllib); */ char *fname = ObjGetPath(ret->mtllib); char *mtl_mem = NULL; size_t mtl_bytes = ObjLoadFile(fname, &mtl_mem); if (mtl_bytes) { ObjMtl *mtl = (ObjMtl *) malloc(sizeof(ObjMtl)); if (!mtl) { fprintf(stderr, "ObjLoadModel() failed, %s:%d\n", __FILE__, __LINE__); perror("malloc"); exit(-1); } /* initialize to zero */ memset(mtl, 0, sizeof(ObjMtl)); /* allocate space for dynamic members */ mtl->map_Ka = (char *) malloc(sizeof(char) * 31); mtl->map_Kd = (char *) malloc(sizeof(char) * 31); if (!mtl->map_Ka || !mtl->map_Kd) { fprintf(stderr, "ObjLoadModel() failed, %s:%d\n", __FILE__, __LINE__); perror("malloc"); exit(-1); } *mtl->map_Ka = '\0'; *mtl->map_Kd = '\0'; char *st = mtl_mem; char *ed = st + mtl_bytes; while (st != ed) { /* skip spaces */ while (*st++ == (char) 0x20) ; if (memcmp(st, "Ns", 2) == 0) sscanf(st, "Ns %f", &mtl->Ns); if (memcmp(st, "Ni", 2) == 0) sscanf(st, "Ni %f", &mtl->Ni); if (memcmp(st, "d", 1) == 0) sscanf(st, "d %f", &mtl->d); if (memcmp(st, "Tr", 2) == 0) sscanf(st, "Tr %f", &mtl->Tr); if (memcmp(st, "Tf", 2) == 0) sscanf(st, "Tf %f %f %f", &mtl->Tf.x, &mtl->Tf.y, &mtl->Tf.z); if (memcmp(st, "illum", 5) == 0) sscanf(st, "illum %d", &mtl->illum); if (memcmp(st, "Ka", 2) == 0) sscanf(st, "Ka %f %f %f", &mtl->Ka.x, &mtl->Ka.y, &mtl->Ka.z); if (memcmp(st, "Kd", 2) == 0) sscanf(st, "Kd %f %f %f", &mtl->Kd.x, &mtl->Kd.y, &mtl->Kd.z); if (memcmp(st, "Ks", 2) == 0) sscanf(st, "Ks %f %f %f", &mtl->Ks.x, &mtl->Ks.y, &mtl->Ks.z); if (memcmp(st, "Ke", 2) == 0) sscanf(st, "Ke %f %f %f", &mtl->Ke.x, &mtl->Ke.y, &mtl->Ke.z); if (memcmp(st, "map_Ka", 6) == 0) sscanf(st, "map_Ka %s", mtl->map_Ka); if (memcmp(st, "map_Kd", 6) == 0) sscanf(st, "map_Kd %s", mtl->map_Kd); /* go to the next line */ while (*st++ != (char) 0x0a) ; } /* set the mtl */ ret->mtl = mtl; /* free the opened mtl file */ free(mtl_mem); } else ret->mtl = NULL; /* should already be NULL but what the hell */ if (fname) free(fname); } /* free the opened file */ free(memory); return ret; } size_t ObjLoadFile(char *szFileName, char **memory) { strcpy(ObjLastFname, szFileName); /* seems useful for tracking */ size_t bytes = 0; FILE *file = fopen(szFileName, "rt"); if (file != NULL) { fseek(file, 0, SEEK_END); size_t end = ftell(file); fseek(file, 0, SEEK_SET); *memory = (char *) malloc(sizeof(char) * end); if (!memory) { fprintf(stderr, "ObjLoadFile() failed, %s:%d\n", __FILE__, __LINE__); perror("malloc"); exit(-1); } bytes = fread(*memory, sizeof(char), end, file); fclose(file); } else { fprintf(stderr, "ObjLoadModel(): failed to open \"%s\", %s:%d\n", szFileName, __FILE__, __LINE__); perror("fopen"); } return bytes; } /* trivial, meh */ void ObjList(ObjModel *model) { int i; printf("%d vertices\n", model->nVertex); for (i = 0; i < model->nVertex; i++) printf("v %f %f %f\n", model->VertexArray[i].x, model->VertexArray[i].y, model->VertexArray[i].z); printf("%d normal vectors\n", model->nNormal); for (i = 0; i < model->nNormal; i++) printf("v %f %f %f\n", model->NormalArray[i].x, model->NormalArray[i].y, model->NormalArray[i].z); printf("%d texture coordinates\n", model->nTexCoord); for (i = 0; i < model->nTexCoord; i++) printf("v %f %f\n", model->TexCoordArray[i].s, model->TexCoordArray[i].t); printf("%d faces\n", model->nFace); for (i = 0; i < model->nFace; i++) { printf("f %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d\n", model->FaceArray[i].Vertex[0], model->FaceArray[i].TexCoord[0], model->FaceArray[i].Normal[0], model->FaceArray[i].Vertex[1], model->FaceArray[i].TexCoord[1], model->FaceArray[i].Normal[1], model->FaceArray[i].Vertex[2], model->FaceArray[i].TexCoord[2], model->FaceArray[i].Normal[2], model->FaceArray[i].Vertex[3], model->FaceArray[i].TexCoord[3], model->FaceArray[i].Normal[3]); } } char *ObjGetPath(const char *fname) { if (fname == NULL || ObjLastFname == NULL) return NULL; char *path = (char *) malloc(sizeof(char) * 101); if (!path) { fprintf(stderr, "ObjGetPath() failed, %s:%d\n", __FILE__, __LINE__); perror("malloc"); exit(-1); } char *delimeter = strrchr(ObjLastFname, '/'); unsigned int offset = delimeter - ObjLastFname; #if 0 fprintf(stdout, "ObjGetPath(): fname = \"%s\", delim = \"%s\", offset = %u" ", ObjLastFname = \"%s\"\n", fname, delimeter, offset, ObjLastFname); #endif strncpy(path, ObjLastFname, offset + 1); strcpy(path + offset + 1, fname); /* strcat(path, fname); */ return path; } void ObjSubmitIndexedVertexArrayQuad(const ObjModel *model) { int i; glVertexPointer(4, GL_FLOAT, 0, model->VertexArray); glNormalPointer(GL_FLOAT, 0, model->NormalArray); /* XXX: why aren't textcoords working :/ ? */ glTexCoordPointer(4, GL_FLOAT, 0, model->TexCoordArray); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); /* vertices */ for (i = 0; i < model->nFace; i++) glDrawElements(GL_QUADS, 4, GL_UNSIGNED_INT, model->FaceArray[i].Vertex); /* normals */ for (i = 0; i < model->nFace; i++) glDrawElements(GL_QUADS, 4, GL_UNSIGNED_INT, model->FaceArray[i].Normal); /* texture coordinates */ for (i = 0; i < model->nFace; i++) glDrawElements(GL_QUADS, 4, GL_UNSIGNED_INT, model->FaceArray[i].TexCoord); #if 0 glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); #endif } /* OpenGL immediate command generator for quads */ void ObjSubmitGLCommandsQuad(const ObjModel *model) { if (!model) return; int i; for (i = 0; i < model->nFace; i++) { glNormal3f(model->NormalArray[model->FaceArray[i].Normal[0]].x, model->NormalArray[model->FaceArray[i].Normal[0]].y, model->NormalArray[model->FaceArray[i].Normal[0]].z ); glTexCoord2f(model->TexCoordArray[model->FaceArray[i].TexCoord[0]].s, model->TexCoordArray[model->FaceArray[i].TexCoord[0]].t); glVertex3f(model->VertexArray[model->FaceArray[i].Vertex[0]].x, model->VertexArray[model->FaceArray[i].Vertex[0]].y, model->VertexArray[model->FaceArray[i].Vertex[0]].z ); glNormal3f(model->NormalArray[model->FaceArray[i].Normal[1]].x, model->NormalArray[model->FaceArray[i].Normal[1]].y, model->NormalArray[model->FaceArray[i].Normal[1]].z ); glTexCoord2f(model->TexCoordArray[model->FaceArray[i].TexCoord[1]].s, model->TexCoordArray[model->FaceArray[i].TexCoord[1]].t); glVertex3f(model->VertexArray[model->FaceArray[i].Vertex[1]].x, model->VertexArray[model->FaceArray[i].Vertex[1]].y, model->VertexArray[model->FaceArray[i].Vertex[1]].z ); glNormal3f(model->NormalArray[model->FaceArray[i].Normal[2]].x, model->NormalArray[model->FaceArray[i].Normal[2]].y, model->NormalArray[model->FaceArray[i].Normal[2]].z ); glTexCoord2f(model->TexCoordArray[model->FaceArray[i].TexCoord[2]].s, model->TexCoordArray[model->FaceArray[i].TexCoord[2]].t); glVertex3f(model->VertexArray[model->FaceArray[i].Vertex[2]].x, model->VertexArray[model->FaceArray[i].Vertex[2]].y, model->VertexArray[model->FaceArray[i].Vertex[2]].z ); glNormal3f(model->NormalArray[model->FaceArray[i].Normal[3]].x, model->NormalArray[model->FaceArray[i].Normal[3]].y, model->NormalArray[model->FaceArray[i].Normal[3]].z ); glTexCoord2f(model->TexCoordArray[model->FaceArray[i].TexCoord[3]].s, model->TexCoordArray[model->FaceArray[i].TexCoord[3]].t); glVertex3f(model->VertexArray[model->FaceArray[i].Vertex[3]].x, model->VertexArray[model->FaceArray[i].Vertex[3]].y, model->VertexArray[model->FaceArray[i].Vertex[3]].z ); } } /* OpenGL immediate command generator for triangles */ void ObjSubmitGLCommandsTriangle(const ObjModel *model) { if (!model) return; int i; for (i = 0; i < model->nFace; i++) { glNormal3f(model->NormalArray[model->FaceArray[i].Normal[0]].x, model->NormalArray[model->FaceArray[i].Normal[0]].y, model->NormalArray[model->FaceArray[i].Normal[0]].z ); glTexCoord2f(model->TexCoordArray[model->FaceArray[i].TexCoord[0]].s, model->TexCoordArray[model->FaceArray[i].TexCoord[0]].t); glVertex3f(model->VertexArray[model->FaceArray[i].Vertex[0]].x, model->VertexArray[model->FaceArray[i].Vertex[0]].y, model->VertexArray[model->FaceArray[i].Vertex[0]].z ); glNormal3f(model->NormalArray[model->FaceArray[i].Normal[1]].x, model->NormalArray[model->FaceArray[i].Normal[1]].y, model->NormalArray[model->FaceArray[i].Normal[1]].z ); glTexCoord2f(model->TexCoordArray[model->FaceArray[i].TexCoord[1]].s, model->TexCoordArray[model->FaceArray[i].TexCoord[1]].t); glVertex3f(model->VertexArray[model->FaceArray[i].Vertex[1]].x, model->VertexArray[model->FaceArray[i].Vertex[1]].y, model->VertexArray[model->FaceArray[i].Vertex[1]].z ); glNormal3f(model->NormalArray[model->FaceArray[i].Normal[2]].x, model->NormalArray[model->FaceArray[i].Normal[2]].y, model->NormalArray[model->FaceArray[i].Normal[2]].z ); glTexCoord2f(model->TexCoordArray[model->FaceArray[i].TexCoord[2]].s, model->TexCoordArray[model->FaceArray[i].TexCoord[2]].t); glVertex3f(model->VertexArray[model->FaceArray[i].Vertex[2]].x, model->VertexArray[model->FaceArray[i].Vertex[2]].y, model->VertexArray[model->FaceArray[i].Vertex[2]].z ); } } void ObjFree(ObjModel *model) { if (model == NULL) return; free(model->NormalArray); free(model->TexCoordArray); free(model->FaceArray); free(model->VertexArray); free(model->mtllib); free(model->objectName); free(model->usemtl); if (model->mtl) { free(model->mtl->map_Ka); free(model->mtl->map_Kd); free(model->mtl); } free(model); }