/* objloader.c * Created by Jon Mayo on 7/26/06. * This software is PUBLIC DOMAIN as of July 2006. No copyright is claimed. * - Jon Mayo <jmayo@rm-f.net> */ /* a small library to load a subset of the Wavefront OBJ model format */ #include <ctype.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <limits.h> #include <unistd.h> #include "objloader.h" #include "model.h" #include "logger.h" #define MAX_LINE_LEN 1024 #define MAX_TESS 6 /* biggest polygon we will tessellate */ static int parse_face_data(const char *filename, unsigned line, struct model *m, struct object **curr_grp, char *buf) { char *tmp, *idx=buf; unsigned i, j; int data[3][MAX_TESS]; if(!*curr_grp) { LOG_DEBUG("%s:%u:face data before group name\n", filename, line); *curr_grp=model_object_create(m, "ungrouped", 1); if(!*curr_grp) { LOG_DEBUG("%s:%u:face data allocation error\n", filename, line); return 0; } } /* for(i=0;i<MAX_TESS;i++) { for(j=0;j<3;j++) { data[j][i]=-1; } } */ /* "v" - vertex index */ /* "vt" - texture index */ /* "vn" - normal index */ /* TODO: error if there are too many sides to the polygon face */ for(i=0;i<MAX_TESS;i++) { unsigned n; while(isspace(*idx)) idx++; if(!*idx) { LOG_INFO("break[%u] %s:%u\n", i, filename, line); break; } for(j=0;j<3;j++) { if(*idx=='/') { /* ignore field */ data[j][i]=-1; idx++; continue; } if(isspace(*idx)) { break; } n=strtol(idx, &tmp, 10); if(idx==tmp) { LOG_ERROR("%s:%u:face data corrupt.\n", filename, line); printf("IDX='%s'\n", idx); return 0; } data[j][i]=n; idx=tmp; if(*idx!='/') { idx++; break; } idx++; } } LOG_DEBUG("does this match? %d/%d/%d %d/%d/%d %d/%d/%d\n", data[0][0], data[1][0], data[2][0], data[0][1], data[1][1], data[2][1], data[0][2], data[1][2], data[2][2]); LOG_DEBUG("\t%s\n", buf); if(data[0][0]==-1 || data[0][1]==-1 || data[0][2]==-1) { LOG_ERROR("%s:%u:Vertex field not supplied %d/%d/%d\n", filename, line, data[0][0], data[0][1], data[0][2]); LOG_INFO("buf='%s'\n", buf); return 0; } LOG_INFO("buf='%s'\n", buf); if(i==3) { if(!model_object_face_add(*curr_grp, data[0][0]-1, data[0][1]-1, data[0][2]-1)) { return 0; } LOG_INFO("data='%u %u %u'\n", data[0][0], data[0][1], data[0][2]); } else if(i==4) { if(!model_object_face_add(*curr_grp, data[0][0]-1, data[0][1]-1, data[0][2]-1)) { return 0; } LOG_INFO("data='%u %u %u'\n", data[0][0], data[0][1], data[0][2]); if(!model_object_face_add(*curr_grp, data[0][2]-1, data[0][3]-1, data[0][0]-1)) { return 0; } LOG_INFO("data='%u %u %u'\n", data[0][2], data[0][3], data[0][0]); } else { LOG_ERROR("Tessellation needed: sides = %u\n", i); } for(;i<MAX_TESS;i++) { for(j=0;j<3;j++) { data[j][i]=-1; } } if(data[1][0]==-1 || data[1][1]==-1 || data[1][2]==-1) { LOG_INFO("%s:%u:Texture field not supplied\n", filename, line); } if(data[2][0]==-1 || data[2][1]==-1 || data[2][2]==-1) { LOG_INFO("%s:%u:Normal field not supplied\n", filename, line); } return 1; } static struct model *load_obj_from_file(FILE *f, const char *filename) { struct model *m; struct object *curr_grp=0; char buf[MAX_LINE_LEN]; char *idx, *cmd, *tmp; unsigned line=0; double v[3]; unsigned i; m=model_create(); while(fgets(buf, sizeof buf, f)) { line++; idx=buf; while(isspace(*idx)) idx++; if(*idx=='#' || !*idx) continue; /* comment or blank line */ cmd=idx; while(*idx && !isspace(*idx)) idx++; if(*idx) { *idx=0; idx++; } if(!strcmp(cmd, "v")) { /* geometry vertex */ for(i=0;i<3;i++) { while(isspace(*idx)) idx++; v[i]=strtod(idx, &tmp); if(idx==tmp) { /* failed */ goto error; } idx=tmp; } /* LOG_INFO("match? \"%s\" [%f,%f,%f]\n", buf+2, v[0], v[1], v[2]); */ if(model_vertex_add(m, v[0], v[1], v[2])<0) { LOG_ERROR("%s:%u:vertex data error\n", filename, line); goto error; } } else if(!strcmp(cmd, "vt")) { /* vertex texture */ for(i=0;i<2;i++) { while(isspace(*idx)) idx++; v[i]=strtod(idx, &tmp); if(idx==tmp) { /* failed */ goto error; } idx=tmp; } LOG_DEBUG("%s:%u:ignoring texture coord [U:%f, V:%f]\n", filename, line, v[0], v[1]); } else if(!strcmp(cmd, "vn")) { /* normal vector */ for(i=0;i<3;i++) { while(isspace(*idx)) idx++; v[i]=strtod(idx, &tmp); if(idx==tmp) { /* failed */ goto error; } idx=tmp; } LOG_DEBUG("%s:%u:ignoring normal [%f, %f, %f]\n", filename, line, v[0], v[1], v[2]); } else if(!strcmp(cmd, "p")) { /* polygon */ } else if(!strcmp(cmd, "l")) { /* line */ } else if(!strcmp(cmd, "f")) { /* face */ if(!parse_face_data(filename, line, m, &curr_grp, idx )) { goto error; } } else if(!strcmp(cmd, "g")) { /* set group name */ tmp=idx; while(*idx && *idx!='\r' && *idx!='\n') idx++; if(*idx) *idx=0; curr_grp=model_object_create(m, tmp, 1); LOG_INFO("curr_grp=%p\n", curr_grp); } else if(!strcmp(cmd, "o")) { /* object name */ tmp=idx; while(*idx && *idx!='\r' && *idx!='\n') idx++; if(*idx) *idx=0; LOG_DEBUG("%s:%u:ignoring object name '%s'\n", filename, line, tmp); #if 0 } else if(!strcmp(cmd, "vp")) { /* point in the parameter space of a curve */ } else if(!strcmp(cmd, "mg")) { /* merging group and merge resolution */ } else if(!strcmp(cmd, "s")) { /* smoothing group */ } else if(!strcmp(cmd, "cstype")) { } else if(!strcmp(cmd, "deg")) { } else if(!strcmp(cmd, "step")) { } else if(!strcmp(cmd, "bmat")) { } else if(!strcmp(cmd, "lod")) { } else if(!strcmp(cmd, "usemap")) { } else if(!strcmp(cmd, "usemtl")) { } else if(!strcmp(cmd, "mtllib")) { } else if(!strcmp(cmd, "shadow_obj")) { } else if(!strcmp(cmd, "trace_obj")) { } else if(!strcmp(cmd, "bsp")) { /* obsolete */ } else if(!strcmp(cmd, "bzp")) { /* obsolete */ } else if(!strcmp(cmd, "cdc")) { /* obsolete */ } else if(!strcmp(cmd, "res")) { /* obsolete */ } else if(!strcmp(cmd, "c_interp")) { } else if(!strcmp(cmd, "bevel")) { } else if(!strcmp(cmd, "curv")) { } else if(!strcmp(cmd, "curv2")) { } else if(!strcmp(cmd, "surf")) { } else if(!strcmp(cmd, "parm")) { } else if(!strcmp(cmd, "trim")) { } else if(!strcmp(cmd, "end")) { } else if(!strcmp(cmd, "hole")) { } else if(!strcmp(cmd, "sp")) { } else if(!strcmp(cmd, "scrv")) { } else if(!strcmp(cmd, "ctech")) { } else if(!strcmp(cmd, "stech")) { } else if(!strcmp(cmd, "con")) { #endif } else { LOG_DEBUG("%s:%u:I don't handle OBJ type '%s'!\n", filename, line, cmd); } } if(!model_verify(m)) { LOG_DEBUG("%s:model verification failed\n", filename); } return m; error: model_free(m); LOG_DEBUG("%s:%u:failed!\n", filename, line); return 0; } struct model *load_obj(const char *filename) { FILE *f; struct model *m; f=fopen(filename, "r"); if(!f) { char cwd[PATH_MAX]; LOG_ERRNO(filename); getcwd(cwd, sizeof cwd); fprintf(stderr, "CWD=\"%s\"\n", cwd); return 0; } m=load_obj_from_file(f, filename); fclose(f); return m; } int save_obj(const char *filename, struct model *m) { static FILE *out; struct object *o; int i, j, k; out=fopen(filename, "w"); for(i=0;i<m->nr_object;i++) { int nr_vertex; float (*vertex)[3]; o=m->object+i; if(o->global_vertex) { nr_vertex=m->nr_vertex; vertex=m->vertex; } else { /* TODO: handle per object vertex tables */ goto error; /* nr_vertex=o->nr_vertex; vertex=o->vertex; */ } printf("[%s]\n", o->tag?o->tag:"noname"); printf(" faces=%d vertices=%d\n", o->nr_face, nr_vertex); for(j=0;j<nr_vertex;j++) fprintf(out, "v %f %f %f\n", vertex[j][0], vertex[j][1], vertex[j][2]); for(j=0;j<o->nr_face;j++) { fprintf(out, "f"); for(k=0;k<3;k++) { fprintf(out, " %u", o->face[j][k]+1); } fprintf(out, "\n"); } } fclose(out); return 1; error: fclose(out); return 0; }