├── LICENSE ├── README.md └── simple_obj.h /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Simple Obj Loader 2 | #### A really simple single header Wavefront .obj loader with glut support. 3 | 4 | Including: 5 | ```c 6 | // Only needed at first include 7 | #define SIMPLE_OBJ_IMP 8 | // glut needs to be included first for drawObj implementation 9 | #include 10 | #include "simple_obj.h" 11 | ``` 12 | 13 | ##### Full docs: 14 | Usage: 15 | 16 | ```c 17 | // Loading a .obj 18 | SimpleObj_t* myObj = loadObj("some_model.obj"); 19 | ``` 20 | 21 | Drawing: 22 | ```c 23 | // GL setup stuff, transforms, ect... 24 | drawObj(myObj); 25 | // More GL stuff 26 | ``` 27 | 28 | Groups: 29 | ```c 30 | // Access the struct for the first group in the file. 31 | ObjGroup_t* group = objDataArrayAccess(&myObj->groups, 0); 32 | // Attributes: name, startFace, endFace, & render. 33 | group->render = false; // don't draw this group. 34 | ``` 35 | 36 | Freeing obj data: 37 | ```c 38 | // This obj is trash 39 | disposeObj(myObj); 40 | // It's where it belongs now 41 | ``` 42 | 43 | Working: 44 | 45 | - Vertices 46 | - Normals 47 | - Texture coords 48 | - Faces 49 | - Basic rendering 50 | 51 | Todo: 52 | - ~~Separate group rendering~~ (done) 53 | - Materials 54 | - Maybe support some more obj features 55 | - ~~Clean up and free stuff~~ (done) 56 | 57 | ##### Example result: 58 | 59 | ![alt text](https://i.imgur.com/ozT39Jh.png "Future gun") 60 | -------------------------------------------------------------------------------- /simple_obj.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_OBJ_H 2 | #define SIMPLE_OBJ_H 3 | 4 | #define _GNU_SOURCE 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define OBJ_COMMENT "#" 13 | #define OBJ_VERTEX "v" 14 | #define OBJ_FACE "f" 15 | #define OBJ_VERTEX_NORMAL "vn" 16 | #define OBJ_VERTEX_TEX_CORD "vt" 17 | #define OBJ_GROUP "g" 18 | 19 | /* Simple Obj Loader 20 | 21 | v0.2.0 22 | Author: MacDue 23 | License: Unlicense 24 | 25 | Loading and drawing Wavefront .obj files. 26 | Supports a subset of the full .obj formal. 27 | 28 | - Vertices (v) 29 | - Vertex normals (vn) 30 | - Texture coordinates (vt) 31 | - Faces (f) 32 | - Groups (g) 33 | - Simple uncached drawing 34 | */ 35 | 36 | typedef struct ObjDataArray { 37 | void* data; 38 | int len; 39 | int nextIndex; 40 | int typeSize; 41 | } ObjDataArray_t; 42 | 43 | typedef struct ObjFaceComponent { 44 | int vertexIndex; 45 | int texCoordIndex; 46 | int normalIndex; 47 | } ObjFaceComponent_t; 48 | 49 | typedef struct ObjVertex { 50 | double x, y, z, w; 51 | } ObjVertex_t; 52 | 53 | typedef struct ObjTexCoord { 54 | double u, v, w; 55 | } ObjTexCoord_t; 56 | 57 | typedef struct ObjVertexNormal { 58 | double x, y, z; 59 | } ObjVertexNormal_t; 60 | 61 | typedef struct ObjGroup { 62 | char* name; 63 | int startFace; 64 | int endFace; 65 | bool render; 66 | } ObjGroup_t; 67 | 68 | typedef struct SimpleObj { 69 | ObjDataArray_t/**/ vertices; 70 | ObjDataArray_t/**/ texCoords; 71 | ObjDataArray_t/**/ normals; 72 | ObjDataArray_t/**/ faces; 73 | ObjDataArray_t/**/ groups; 74 | // ObjDataArray_t/**/ lines; Not implemented. 75 | } SimpleObj_t; 76 | 77 | SimpleObj_t* loadObj(char* file_name); 78 | void disposeObj(SimpleObj_t* obj); 79 | void drawObj(SimpleObj_t* obj); 80 | void* objDataArrayAccess(ObjDataArray_t* objDataArray, int index); 81 | 82 | // Will write to returnArray. 83 | #define STANDARD_ARRAY_PARSER(arrayType_t, strtoFunc) \ 84 | static int \ 85 | parseArray_ ## arrayType_t \ 86 | (char* arrayStr, int minSize, arrayType_t* returnArray) { \ 87 | replaceDelims(arrayStr); \ 88 | ObjDataArray_t doubleData = {}; \ 89 | initObjDataArray( \ 90 | &doubleData, sizeof (arrayType_t), minSize, returnArray); \ 91 | char* next = NULL; \ 92 | char* previous = arrayStr; \ 93 | for (;;) { \ 94 | arrayType_t d = strtoFunc; \ 95 | if (next == previous) { \ 96 | break; \ 97 | } \ 98 | previous = next; \ 99 | objDataArrayAppend(&doubleData,&d); \ 100 | } \ 101 | return doubleData.nextIndex; \ 102 | } 103 | 104 | #endif 105 | 106 | #ifdef SIMPLE_OBJ_IMP 107 | 108 | static const char extra_delims[] = {'/', '\0'}; 109 | 110 | // Sadly needed to fix strtol 111 | static void replaceDelims(char* str) { 112 | int i = 0; 113 | while (str[i]) { 114 | int delmin = 0; 115 | while(extra_delims[delmin]) { 116 | if (str[i] == extra_delims[delmin++]) { 117 | str[i] = ' '; 118 | } 119 | } 120 | i++; 121 | } 122 | } 123 | 124 | // Needed to check trailing whitespace on faces 125 | static bool strIsSpace(char* str) { 126 | while (*str) { 127 | if (!isspace(*str)) return false; 128 | str++; 129 | } 130 | return true; 131 | } 132 | 133 | /** 134 | Access a element in a one of the objs arrays. 135 | You must cast the result to the type you were accssing e.g. ObjVertex_t*. 136 | 137 | @param ObjDataArray_t* objDataArray An array e.g. obj->vertices 138 | @param int index An index into that array 139 | 140 | @return void* The accessed element 141 | */ 142 | inline void* objDataArrayAccess(ObjDataArray_t* objDataArray, int index) { 143 | return (void*)((size_t)objDataArray->data+index*objDataArray->typeSize); 144 | } 145 | 146 | static ObjDataArray_t* 147 | objDataArrayAppend (ObjDataArray_t* objDataArray, void* data) { 148 | // Type safey is for the weak. 149 | if (objDataArray->nextIndex >= objDataArray->len) { 150 | int newLen = objDataArray->len * 2; 151 | objDataArray->data 152 | = realloc(objDataArray->data, newLen * objDataArray->typeSize); 153 | objDataArray->len = newLen; 154 | } 155 | // Must directly copy memory to avoid any casting. 156 | memcpy ( 157 | objDataArrayAccess(objDataArray, objDataArray->nextIndex), 158 | data, objDataArray->typeSize 159 | ); 160 | objDataArray->nextIndex++; 161 | return objDataArray; 162 | } 163 | 164 | static inline void objDataArrayDispose(ObjDataArray_t* objDataArray) { 165 | free(objDataArray->data); 166 | } 167 | 168 | static ObjDataArray_t* 169 | initObjDataArray (ObjDataArray_t* objDataArray, 170 | int typeSize, int reserve, void* array) { 171 | objDataArray->data = array == NULL ? malloc(typeSize * reserve) : array; 172 | objDataArray->len = reserve; 173 | objDataArray->nextIndex = 0; 174 | objDataArray->typeSize = typeSize; 175 | return objDataArray; 176 | } 177 | 178 | // Create some generic parsers 179 | STANDARD_ARRAY_PARSER(double, strtod(previous, &next)) 180 | STANDARD_ARRAY_PARSER(long, strtol(previous, &next, 10)) 181 | 182 | static SimpleObj_t* newSimpleObj(void) { 183 | SimpleObj_t* obj = malloc(sizeof (SimpleObj_t)); 184 | initObjDataArray(&obj->vertices, sizeof (ObjVertex_t), 100, NULL); 185 | initObjDataArray(&obj->texCoords, sizeof (ObjTexCoord_t), 100, NULL); 186 | initObjDataArray(&obj->normals, sizeof (ObjVertexNormal_t), 100, NULL); 187 | initObjDataArray(&obj->faces, sizeof (ObjDataArray_t), 100, NULL); 188 | // initObjDataArray(&obj->lines, sizeof (int*), 100, NULL); 189 | initObjDataArray(&obj->groups, sizeof (ObjGroup_t), 100, NULL); 190 | return obj; 191 | } 192 | 193 | static inline int fpeek(FILE* file) { 194 | int c = fgetc(file); 195 | ungetc(c, file); 196 | return c; 197 | } 198 | 199 | static long longDataBuffer[4]; 200 | static double dataInputBuffer[4]; 201 | 202 | /* 203 | Load a Wavefront .obj file. 204 | 205 | @param char* fileName A path to a .obj 206 | 207 | @return SimpleObj_t* a pointer to a loaded .obj 208 | or NULL if the file could not be opened. 209 | */ 210 | SimpleObj_t* loadObj(char* fileName) { 211 | 212 | FILE *obj_fp; 213 | obj_fp = fopen (fileName, "r"); 214 | if (obj_fp == NULL) 215 | { 216 | fprintf(stderr, "Unable to open obj file.\n"); 217 | return NULL; 218 | } 219 | 220 | SimpleObj_t* obj = newSimpleObj(); 221 | // strdup for ease of freeing 222 | ObjGroup_t currentGroup = { .name = strdup("Default"), 223 | .startFace = 0, 224 | .endFace = 0, 225 | .render = true }; 226 | 227 | size_t lineLen; 228 | char* line = NULL; 229 | size_t len = 0; 230 | // Only set when needed. Does not always contain correct value. 231 | bool peekEof = false; 232 | 233 | while ((lineLen = getline(&line, &len, obj_fp)) != -1) { 234 | char* lineType = strtok(line, " "); 235 | char* remaining = strtok(NULL, "\n"); 236 | if (lineType == NULL || remaining == NULL) { 237 | /* Junk */ 238 | continue; 239 | } 240 | if (strcmp(lineType, OBJ_GROUP) == 0 241 | || (peekEof = (fpeek(obj_fp) == EOF))) { 242 | /* Groups */ 243 | if (obj->faces.nextIndex - currentGroup.startFace > 0) { 244 | currentGroup.endFace = obj->faces.nextIndex; 245 | objDataArrayAppend(&obj->groups, ¤tGroup); 246 | } else { 247 | free(currentGroup.name); 248 | } 249 | if (!peekEof) { 250 | currentGroup.name = strdup(remaining); 251 | currentGroup.startFace = obj->faces.nextIndex; 252 | } else goto nextCase; // A hack. Continue down the if/else. 253 | } else nextCase: if (strcmp(lineType, OBJ_VERTEX) == 0) { 254 | /* Vertices */ 255 | int vertexLen = parseArray_double(remaining, 4, dataInputBuffer); 256 | assert(vertexLen == 3 || vertexLen == 4); 257 | ObjVertex_t vertex = { .x = dataInputBuffer[0], 258 | .y = dataInputBuffer[1], 259 | .z = dataInputBuffer[2], 260 | .w = vertexLen == 4 ? dataInputBuffer[3] : 1 }; 261 | objDataArrayAppend(&obj->vertices, &vertex); 262 | } else if (strcmp(lineType, OBJ_VERTEX_NORMAL) == 0) { 263 | /* Normals */ 264 | int normalLen = parseArray_double(remaining, 3, dataInputBuffer); 265 | assert(normalLen == 3); 266 | ObjVertexNormal_t normal = { .x = dataInputBuffer[0], 267 | .y = dataInputBuffer[1], 268 | .z = dataInputBuffer[2] }; 269 | objDataArrayAppend(&obj->normals, &normal); 270 | } else if (strcmp(lineType, OBJ_VERTEX_TEX_CORD) == 0) { 271 | /* Texture coordinates */ 272 | int textCoordLen = parseArray_double(remaining, 3, dataInputBuffer); 273 | assert(textCoordLen == 2 || textCoordLen == 3); 274 | ObjTexCoord_t texCoord = { .u = dataInputBuffer[0], 275 | .v = dataInputBuffer[1], 276 | .w = textCoordLen > 2 277 | ? dataInputBuffer[2] : 0}; 278 | objDataArrayAppend(&obj->texCoords, &texCoord); 279 | } else if (strcmp(lineType, OBJ_FACE) == 0) { 280 | /* Faces */ 281 | ObjDataArray_t face = {}; 282 | // Most faces seem to have at most 4 vertices. 283 | initObjDataArray(&face, sizeof(ObjFaceComponent_t), 4, NULL); 284 | bool vertexAndNormalOnly = strstr(remaining, "//"); 285 | int vnIndexBuff = vertexAndNormalOnly ? 1 : 2; 286 | char* faceComponentStr = strtok(remaining, " "); 287 | while (faceComponentStr != NULL && !strIsSpace(faceComponentStr)) { 288 | int faceCompLen = parseArray_long(faceComponentStr, 3, longDataBuffer); 289 | assert(faceCompLen >= 1 && faceCompLen <= 3); 290 | // Length 1 = vertex 291 | // Length 2 = vertex//normal or vertex/tex 292 | // Length 3 = vertex/tex/normal 293 | ObjFaceComponent_t faceComponent = { .vertexIndex = longDataBuffer[0], 294 | .texCoordIndex 295 | = faceCompLen > 1 296 | && !vertexAndNormalOnly 297 | ? longDataBuffer[1] : -1, 298 | .normalIndex 299 | = vnIndexBuff < faceCompLen 300 | ? longDataBuffer[vnIndexBuff] 301 | : -1 302 | }; 303 | objDataArrayAppend(&face, &faceComponent); 304 | faceComponentStr = strtok(NULL, " "); 305 | } 306 | objDataArrayAppend(&obj->faces, &face); 307 | if (peekEof) { 308 | // If the last line of the file is a face then the last group will 309 | // miss that face unless corrected here. 310 | ObjGroup_t* lastGroup 311 | = objDataArrayAccess(&obj->groups, obj->groups.nextIndex-1); 312 | lastGroup->endFace++; 313 | } 314 | } 315 | #ifdef DEBUG 316 | else if (strcmp(lineType, OBJ_COMMENT) != 0) { 317 | fprintf(stderr, "Unknown line type '%s'\n", lineType); 318 | } 319 | #endif 320 | } 321 | 322 | #ifdef DEBUG 323 | printf( 324 | "obj: vertices %d, texture coords %d, normals %d, faces %d, groups %d\n", 325 | obj->vertices.nextIndex, 326 | obj->texCoords.nextIndex, 327 | obj->normals.nextIndex, 328 | obj->faces.nextIndex, 329 | obj->groups.nextIndex); 330 | #endif 331 | 332 | free(line); 333 | fclose (obj_fp); 334 | 335 | return obj; 336 | } 337 | 338 | /** 339 | Freeing allocations & cleaning up after an obj. 340 | 341 | @param SimpleObj_t* obj A pointer to an obj 342 | */ 343 | void disposeObj(SimpleObj_t* obj) { 344 | objDataArrayDispose(&obj->vertices); 345 | objDataArrayDispose(&obj->texCoords); 346 | objDataArrayDispose(&obj->normals); 347 | int f; 348 | for (f = 0; f < obj->faces.nextIndex; f++) { 349 | objDataArrayDispose((ObjDataArray_t*)objDataArrayAccess(&obj->faces, f)); 350 | } 351 | int g; 352 | for (g = 0; g < obj->groups.nextIndex; g++) { 353 | ObjGroup_t* group = objDataArrayAccess(&obj->groups, g); 354 | free(group->name); 355 | } 356 | objDataArrayDispose(&obj->groups); 357 | objDataArrayDispose(&obj->faces); 358 | // objDataArrayDispose(&obj->lines); 359 | free(obj); 360 | } 361 | 362 | #ifdef __GLUT_H__ 363 | /** 364 | Naively drawing a loaded obj. 365 | May work with more than libglut/libGL but is untested with anything else. 366 | 367 | @param SimpleObj_t* a pointer to an obj 368 | */ 369 | void drawObj(SimpleObj_t* obj) { 370 | int g; 371 | for (g = 0; g < obj->groups.nextIndex; g++) { 372 | ObjGroup_t* group = objDataArrayAccess(&obj->groups, g); 373 | if (!group->render) continue; 374 | int f; 375 | for (f = group->startFace; f < group->endFace; f++) { 376 | ObjDataArray_t* face = objDataArrayAccess(&obj->faces, f); 377 | glBegin(GL_POLYGON); 378 | int f_c; 379 | for (f_c = 0; f_c < face->nextIndex; f_c++) { 380 | ObjFaceComponent_t* faceComp = objDataArrayAccess(face, f_c); 381 | ObjVertex_t* vertex 382 | = objDataArrayAccess(&obj->vertices, faceComp->vertexIndex-1); 383 | if (faceComp->normalIndex > 0) { 384 | ObjVertexNormal_t* normal 385 | = objDataArrayAccess(&obj->normals, faceComp->normalIndex-1); 386 | glNormal3d(normal->x, normal->y, normal->z); 387 | } 388 | if (faceComp->texCoordIndex > 0) { 389 | ObjTexCoord_t* texCoord 390 | = objDataArrayAccess(&obj->texCoords, faceComp->texCoordIndex-1); 391 | glTexCoord3d(texCoord->u, texCoord->v, texCoord->w); 392 | } 393 | glVertex4d(vertex->x, vertex->y, vertex->z, vertex->w); 394 | } 395 | glEnd(); 396 | } 397 | } 398 | } 399 | #endif 400 | #endif 401 | --------------------------------------------------------------------------------