void SceneParser::parseFile() { // // at the top level, the scene can have a camera, // background color and a group of objects // (we add lights and other things in future assignments) // char token[MAX_PARSER_TOKEN_LENGTH]; while (getToken(token)) { if (!strcmp(token, "OrthographicCamera")) { parseOrthographicCamera(); } else if (!strcmp(token, "PerspectiveCamera")) { parsePerspectiveCamera(); } else if (!strcmp(token, "Background")) { parseBackground(); } else if (!strcmp(token, "Lights")) { parseLights(); } else if (!strcmp(token, "Materials")) { parseMaterials(); } else if (!strcmp(token, "Group")) { group = parseGroup(); } else { printf("Unknown token in parseFile: '%s'\n", token); exit(0); } } }
void scene::parse(const char *filename) { //some default values in case parsing fails myObjGroup = NULL; bgColor = Vec3f(0.5,0.5,0.5); ambLight = Vec3f(0.5,0.5,0.5); eye = Vec3f(0,0,0); lookAt = Vec3f(0,0,-1); up = Vec3f(0,1,0); fovy = 45; file = NULL; curline = 1; //the file-extension needs to be "ray" assert(filename != NULL); const char *ext = &filename[strlen(filename)-4]; assert(!strcmp(ext,".ray")); fopen_s(&file, filename, "r"); assert(file != NULL); char token[MAX_PARSER_TOKEN_LENGTH]; //prime the parser pipe parse_char = fgetc(file); while (getToken(token)) { if(!strcmp(token, "Background")) parseBackground(); else if(!strcmp(token, "Camera")) parseCamera(); else if(!strcmp(token, "Materials")) parseMaterials(); else if(!strcmp(token, "Group")) myObjGroup = parseGroup(); else if(!strcmp(token, "Lights")) parseLights(); else { cout<<"Unknown token in parseFile: '"<<token<< "' at input line "<<curline<<"\n"; exit(-1); } } }
OBJMesh* objMeshLoad(const char* filename) { int linenum = 0; int warning = 0; int fatalError = 0; //open the ascii file FILE* file = fopen(filename, "r"); //check it actually opened if (!file) { perror(filename); return NULL; } //the mesh we're going to return OBJMesh* mesh = (OBJMesh*)malloc(sizeof(OBJMesh)); memset(mesh, 0, sizeof(OBJMesh)); //we don't know how much data the obj file has, so we start with one and keep realloc-ing double ReallocArray vertices, faceSets, vertexHash, positions, normals, texCoords, triangles; initArray(&vertexHash, sizeof(VertHash*)); //array of hash record pointers (for freeing) initArray(&positions, sizeof(float) * 3); initArray(&normals, sizeof(float) * 3); initArray(&texCoords, sizeof(float) * 2); initArray(&triangles, sizeof(unsigned int) * 3); initArray(&faceSets, sizeof(OBJFaceSet)); vertices.size = 0; //vertices are allocated later (at first face) //obj indices start at 1. we'll use the zero element for "error", giving with zero data positions.size = 1; normals.size = 1; texCoords.size = 1; allocArray(&positions); allocArray(&normals); allocArray(&texCoords); memset(positions.data, 0, positions.blockSize); memset(normals.data, 0, normals.blockSize); memset(texCoords.data, 0, texCoords.blockSize); //vertex combinations v/t/n are not always the same index. different vertex //combinations are hashed and reused, saving memory //see uthash.h (http://uthash.sourceforge.net/) VertHash* vertexRecords = NULL; //materials are referenced by name. this hash maps a name to material index MatHash* materialRecords = NULL; //whether the mesh contains normals or texture coordinates, and hence //the vertex data stride, is decided at the first face line ("f ...") int reachedFirstFace = 0; //current shading state int smoothShaded = 1; //start reading, line by line char* tmpTok; char line[OBJ_MAX_LINE_LEN]; while (fgets(line, OBJ_MAX_LINE_LEN, file) != NULL) { //split line by spaces strtok_r(line, " ", &tmpTok); //what data does this line give us, if any? if (line[0] == 'v') { //this line contains vertex data if (line[1] == '\0') //\0 as strtok replaced the space { //position data. allocate more memory if needed positions.size++; allocArray(&positions); //read data from string ((float*)positions.data)[(positions.size-1)*3+0] = readTokFloat(&tmpTok, &warning); ((float*)positions.data)[(positions.size-1)*3+1] = readTokFloat(&tmpTok, &warning); ((float*)positions.data)[(positions.size-1)*3+2] = readTokFloat(&tmpTok, &warning); } else if (line[1] == 'n') { //normal data. allocate more memory if needed normals.size++; allocArray(&normals); //read data from string ((float*)normals.data)[(normals.size-1)*3+0] = readTokFloat(&tmpTok, &warning); ((float*)normals.data)[(normals.size-1)*3+1] = readTokFloat(&tmpTok, &warning); ((float*)normals.data)[(normals.size-1)*3+2] = readTokFloat(&tmpTok, &warning); } else if (line[1] == 't') { //texture data. allocate more memory if needed texCoords.size++; allocArray(&texCoords); //read data from string ((float*)texCoords.data)[(texCoords.size-1)*2+0] = readTokFloat(&tmpTok, &warning); ((float*)texCoords.data)[(texCoords.size-1)*2+1] = readTokFloat(&tmpTok, &warning); } } else if (line[0] == 'f') { //this line contains face data. this may have many vertices //but we just want triangles. so we triangulate. //NOTE: ALL vertex data must be given before being referenced by a face //NOTE: At least one vt or vn must be specified before the first f, or the rest will be ignored if (!reachedFirstFace) { //must have previously specified vertex positions if (positions.size == 1) { fatalError = 1; break; } //calculate vertex stride mesh->hasNormals = (normals.size > 1) ? 1 : 0; mesh->hasTexCoords = (texCoords.size > 1) ? 1 : 0; mesh->normalOffset = 3 * sizeof(float); mesh->texcoordOffset = mesh->normalOffset + mesh->hasNormals * 3 * sizeof(float); mesh->stride = mesh->texcoordOffset + mesh->hasTexCoords * 2 * sizeof(float); initArray(&vertices, mesh->stride); reachedFirstFace = 1; } //start by extracting groups of vertex data (pos/tex/norm) int i = 0; char* vert[OBJ_MAX_POLYGON]; while ((vert[i] = strtok_r(NULL, " ", &tmpTok)) != NULL && i < OBJ_MAX_POLYGON) ++i; //triangulate using the "fan" method int triVert = 0; //triVert contains the current face's vertex index. may not equal v as vertices can be ignored. int triangulate[2]; for (int v = 0; v < i; ++v) { //split groups by "/" and store in inds[3] int t = 0; int inds[3]; char* tok = strtok_r(vert[v], "/", &tmpTok); while (tok != NULL && t < 3) { inds[t++] = atoi(tok); tok = strtok_r(NULL, "/", &tmpTok); } while (t < 3) inds[t++] = 0; //set provided, yet unused indices as invalid. this //prevents unnecessary unique vertices being created if (!mesh->hasTexCoords) inds[1] = -1; if (!mesh->hasNormals) inds[2] = -1; //ignore vertex if position indices are out of bounds if (inds[0] < 0 || inds[0] >= positions.size) { warning = 1; continue; } //use zero for out of bound normals and texture coordinates if ((mesh->hasTexCoords && (inds[1] < 0 || inds[1] >= texCoords.size)) || (mesh->hasNormals && (inds[2] < 0 || inds[2] >= normals.size))) { warning = 1; } //since vertices will be reused a lot, we need to hash the v/t/n combination int uniqueVertIndex; //check if the vertex already exists in hash VertHash h; VertHash* found = NULL; memset(&h, 0, sizeof(VertHash)); h.key.v = inds[0]; h.key.t = inds[1]; h.key.n = inds[2]; //printf("looking up %i/%i/%i\n", h.key.v, h.key.t, h.key.n); HASH_FIND(hh, vertexRecords, &h.key, sizeof(VertHashKey), found); //printf("done looking up\n"); if (found) { //found. use that vertex //printf("vert %i/%i/%i already exists as %i\n", inds[0], inds[1], inds[2], found->index); uniqueVertIndex = found->index; } else { //printf("new vert %i/%i/%i\n", inds[0], inds[1], inds[2]); //not found. create a new vertex uniqueVertIndex = vertices.size++; allocArray(&vertices); //copy data for vertex memcpy(((float*)vertices.data) + uniqueVertIndex * mesh->stride / sizeof(float), ((float*)positions.data) + inds[0] * 3, sizeof(float) * 3); if (mesh->hasTexCoords) memcpy(((float*)vertices.data) + (uniqueVertIndex * mesh->stride + mesh->texcoordOffset) / sizeof(float), ((float*)texCoords.data) + inds[1] * 2, sizeof(float) * 2); if (mesh->hasNormals) memcpy(((float*)vertices.data) + (uniqueVertIndex * mesh->stride + mesh->normalOffset) / sizeof(float), ((float*)normals.data) + inds[2] * 3, sizeof(float) * 3); //add vertex to hash table vertexHash.size++; allocArray(&vertexHash); VertHash* newRecord = (VertHash*)malloc(sizeof(VertHash)); ((VertHash**)vertexHash.data)[vertexHash.size-1] = newRecord; //store pointer to quickly free hash records later h.index = uniqueVertIndex; *newRecord = h; HASH_ADD(hh, vertexRecords, key, sizeof(VertHashKey), newRecord); } if (triVert == 0) { //store the first vertex triangulate[0] = uniqueVertIndex; } else if (triVert > 1) { //printf("tri %i->%i->%i\n", triangulate[0], triangulate[1], uniqueVertIndex); //this is at least the 3rd vertex - we have a new triangle to add //always create triangles between the current, previous and first vertex triangles.size++; allocArray(&triangles); //read data from string ((unsigned int*)triangles.data)[(triangles.size-1)*3+0] = triangulate[0]; ((unsigned int*)triangles.data)[(triangles.size-1)*3+1] = triangulate[1]; ((unsigned int*)triangles.data)[(triangles.size-1)*3+2] = uniqueVertIndex; } //store the last vertex triangulate[1] = uniqueVertIndex; ++triVert; } } else if (strcmp(line, "usemtl") == 0) { //the material state has changed - create a new faceset char* mtlname = strtok_r(NULL, " ", &tmpTok); trimRight(mtlname); //find the corresponding material MatHash* matRecord; HASH_FIND_STR(materialRecords, mtlname, matRecord); if (matRecord) { //printf("material: '%s'\n", mtlname); setFaceSet(&faceSets, matRecord->index, smoothShaded, triangles.size * 3); } else { warning = 1; printf("Undefined material: '%s'\n", mtlname); setFaceSet(&faceSets, -1, -1, triangles.size * 3); } } else if (strcmp(line, "mtllib") == 0) { //load all materials from the given .mtl file if (mesh->numMaterials > 0) { fatalError = 1; //shouldn't specify multiple .mtl files break; } char* mtlname = strtok_r(NULL, " ", &tmpTok); trimRight(mtlname); if (mtlname) parseMaterials(mesh, mtlname); //add all materials to the hash MatHash* matRecord; for (int m = 0; m < mesh->numMaterials; ++m) { HASH_FIND_STR(materialRecords, mesh->materials[m].name, matRecord); if (matRecord) { warning = 1; printf("Multiple materials with name '%s'\n", mesh->materials[m].name); continue; //can't have multiple definitions of the same material } matRecord = (MatHash*)malloc(sizeof(MatHash)); matRecord->name = mesh->materials[m].name; matRecord->index = m; HASH_ADD_KEYPTR(hh, materialRecords, matRecord->name, strlen(matRecord->name), matRecord); } } else if (line[0] == 's') { #if !IGNORE_SMOOTHING //smooth shading has been changed - create a new faceset char* smooth = strtok_r(NULL, " ", &tmpTok); trimRight(smooth); smoothShaded = atoi(smooth); if (strcmp(smooth, "on") == 0) smoothShaded = 1; if (strcmp(smooth, "off") == 0) smoothShaded = 0; setFaceSet(&faceSets, -1, smoothShaded, triangles.size * 3); #endif } //options o and g are ignored linenum++; //for warnings/errors } setFaceSet(&faceSets, -1, -1, triangles.size * 3); //update end of final faceset //fill the rest of the mesh structure exactAllocArray(&vertices); exactAllocArray(&triangles); exactAllocArray(&faceSets); mesh->vertices = (float*)vertices.data; mesh->indices = (unsigned int*)triangles.data; mesh->facesets = (OBJFaceSet*)faceSets.data; mesh->numVertices = vertices.size; mesh->numIndices = triangles.size * 3; mesh->numFacesets = faceSets.size; //cleanup //NOTE: vertices and indices are used in the returned mesh data and are not freed MatHash* matRecord; MatHash* matTmp; HASH_ITER(hh, materialRecords, matRecord, matTmp) { HASH_DEL(materialRecords, matRecord); free(matRecord); }