#include #include #include #include "objparser.hpp" namespace ObjParser { static bool obj_parseline(const char *line, ObjData &data) { #define EATWS() while (*line == ' ' || *line == '\t') ++ line if (*line == 0) return true; // Ignore whitespaces at the beginning of the line. //FIXME is this a good idea? EATWS(); char c1 = *line ++; switch (c1) { case '#': // Comment, ignore the rest of the line. break; case 'v': { // Parse vertex geometry (position, normal, texture coordinates) char c2 = *line ++; switch (c2) { case 't': { // vt - vertex texture parameter // u v [w], w == 0 (or w == 1) char c2 = *line ++; if (c2 != ' ' && c2 != '\t') return false; EATWS(); char *endptr = 0; double u = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; line = endptr; EATWS(); double v = 0; if (*line != 0) { v = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); } double w = 0; if (*line != 0) { w = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); } if (*line != 0) return false; data.textureCoordinates.push_back((float)u); data.textureCoordinates.push_back((float)v); data.textureCoordinates.push_back((float)w); break; } case 'n': { // vn - vertex normal // x y z char c2 = *line ++; if (c2 != ' ' && c2 != '\t') return false; EATWS(); char *endptr = 0; double x = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; line = endptr; EATWS(); double y = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; line = endptr; EATWS(); double z = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); if (*line != 0) return false; data.normals.push_back((float)x); data.normals.push_back((float)y); data.normals.push_back((float)z); break; } case 'p': { // vp - vertex parameter char c2 = *line ++; if (c2 != ' ' && c2 != '\t') return false; EATWS(); char *endptr = 0; double u = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); double v = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); double w = 0; if (*line != 0) { w = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); } if (*line != 0) return false; data.parameters.push_back((float)u); data.parameters.push_back((float)v); data.parameters.push_back((float)w); break; } default: { // v - vertex geometry if (c2 != ' ' && c2 != '\t') return false; EATWS(); char *endptr = 0; double x = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; line = endptr; EATWS(); double y = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; line = endptr; EATWS(); double z = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); double w = 1.0; if (*line != 0) { w = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); } if (*line != 0) return false; data.coordinates.push_back((float)x); data.coordinates.push_back((float)y); data.coordinates.push_back((float)z); data.coordinates.push_back((float)w); break; } } break; } case 'f': { // face EATWS(); if (*line == 0) return false; // number of vertices of this face int n = 0; // current vertex to be parsed ObjVertex vertex; char *endptr = 0; while (*line != 0) { // Parse a single vertex reference. vertex.coordIdx = 0; vertex.normalIdx = 0; vertex.textureCoordIdx = 0; vertex.coordIdx = strtol(line, &endptr, 10); // Coordinate has to be defined if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != '/' && *endptr != 0)) return false; line = endptr; if (*line == '/') { ++ line; // Texture coordinate index may be missing after a 1st slash, but then the normal index has to be present. if (*line != '/') { // Parse the texture coordinate index. vertex.textureCoordIdx = strtol(line, &endptr, 10); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != '/' && *endptr != 0)) return false; line = endptr; } if (*line == '/') { // Parse normal index. ++ line; vertex.normalIdx = strtol(line, &endptr, 10); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; } } if (vertex.coordIdx < 0) vertex.coordIdx += data.coordinates.size() / 4; else -- vertex.coordIdx; if (vertex.normalIdx < 0) vertex.normalIdx += data.normals.size() / 3; else -- vertex.normalIdx; if (vertex.textureCoordIdx < 0) vertex.textureCoordIdx += data.textureCoordinates.size() / 3; else -- vertex.textureCoordIdx; data.vertices.push_back(vertex); EATWS(); } vertex.coordIdx = -1; vertex.normalIdx = -1; vertex.textureCoordIdx = -1; data.vertices.push_back(vertex); break; } case 'm': { if (*(line ++) != 't' || *(line ++) != 'l' || *(line ++) != 'l' || *(line ++) != 'i' || *(line ++) != 'b') return false; // mtllib [external .mtl file name] // printf("mtllib %s\r\n", line); EATWS(); data.mtllibs.push_back(std::string(line)); break; } case 'u': { if (*(line ++) != 's' || *(line ++) != 'e' || *(line ++) != 'm' || *(line ++) != 't' || *(line ++) != 'l') return false; // usemtl [material name] // printf("usemtl %s\r\n", line); EATWS(); ObjUseMtl usemtl; usemtl.vertexIdxFirst = data.vertices.size(); usemtl.name = line; data.usemtls.push_back(usemtl); break; } case 'o': { // o [object name] EATWS(); const char *name = line; while (*line != ' ' && *line != '\t' && *line != 0) ++ line; // copy name to line. EATWS(); if (*line != 0) return false; ObjObject object; object.vertexIdxFirst = data.vertices.size(); object.name = line; data.objects.push_back(object); break; } case 'g': { // g [group name] // printf("group %s\r\n", line); ObjGroup group; group.vertexIdxFirst = data.vertices.size(); group.name = line; data.groups.push_back(group); break; } case 's': { // s 1 / off char c2 = *line ++; if (c2 != ' ' && c2 != '\t') return false; EATWS(); char *endptr = 0; long g = strtol(line, &endptr, 10); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); if (*line != 0) return false; ObjSmoothingGroup group; group.vertexIdxFirst = data.vertices.size(); group.smoothingGroupID = g; data.smoothingGroups.push_back(group); break; } default: printf("ObjParser: Unknown command: %c\r\n", c1); break; } return true; } bool objparse(const char *path, ObjData &data) { FILE *pFile = boost::nowide::fopen(path, "rt"); if (pFile == 0) return false; try { char buf[65536 * 2]; size_t len = 0; size_t lenPrev = 0; while ((len = ::fread(buf + lenPrev, 1, 65536, pFile)) != 0) { len += lenPrev; size_t lastLine = 0; for (size_t i = 0; i < len; ++ i) if (buf[i] == '\r' || buf[i] == '\n') { buf[i] = 0; char *c = buf + lastLine; while (*c == ' ' || *c == '\t') ++ c; obj_parseline(c, data); lastLine = i + 1; } lenPrev = len - lastLine; memmove(buf, buf + lastLine, lenPrev); } } catch (std::bad_alloc &ex) { printf("Out of memory\r\n"); } ::fclose(pFile); // printf("vertices: %d\r\n", data.vertices.size() / 4); // printf("coords: %d\r\n", data.coordinates.size()); return true; } template bool savevector(FILE *pFile, const std::vector &v) { size_t cnt = v.size(); ::fwrite(&cnt, 1, sizeof(cnt), pFile); //FIXME sizeof(T) works for data types leaving no gaps in the allocated vector because of alignment of the T type. if (! v.empty()) ::fwrite(&v.front(), 1, sizeof(T) * cnt, pFile); return true; } bool savevector(FILE *pFile, const std::vector &v) { size_t cnt = v.size(); ::fwrite(&cnt, 1, sizeof(cnt), pFile); for (size_t i = 0; i < cnt; ++ i) { size_t len = v[i].size(); ::fwrite(&len, 1, sizeof(cnt), pFile); ::fwrite(v[i].c_str(), 1, len, pFile); } return true; } template bool savevectornameidx(FILE *pFile, const std::vector &v) { size_t cnt = v.size(); ::fwrite(&cnt, 1, sizeof(cnt), pFile); for (size_t i = 0; i < cnt; ++ i) { ::fwrite(&v[i].vertexIdxFirst, 1, sizeof(int), pFile); size_t len = v[i].name.size(); ::fwrite(&len, 1, sizeof(cnt), pFile); ::fwrite(v[i].name.c_str(), 1, len, pFile); } return true; } template bool loadvector(FILE *pFile, std::vector &v) { v.clear(); size_t cnt = 0; if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1) return false; //FIXME sizeof(T) works for data types leaving no gaps in the allocated vector because of alignment of the T type. if (cnt != 0) { v.assign(cnt, T()); if (::fread(&v.front(), sizeof(T), cnt, pFile) != cnt) return false; } return true; } bool loadvector(FILE *pFile, std::vector &v) { v.clear(); size_t cnt = 0; if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1) return false; v.reserve(cnt); for (size_t i = 0; i < cnt; ++ i) { size_t len = 0; if (::fread(&len, sizeof(len), 1, pFile) != 1) return false; std::string s(" ", len); if (::fread(const_cast(s.c_str()), 1, len, pFile) != len) return false; v.push_back(std::move(s)); } return true; } template bool loadvectornameidx(FILE *pFile, std::vector &v) { v.clear(); size_t cnt = 0; if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1) return false; v.assign(cnt, T()); for (size_t i = 0; i < cnt; ++ i) { if (::fread(&v[i].vertexIdxFirst, sizeof(int), 1, pFile) != 1) return false; size_t len = 0; if (::fread(&len, sizeof(len), 1, pFile) != 1) return false; v[i].name.assign(" ", len); if (::fread(const_cast(v[i].name.c_str()), 1, len, pFile) != len) return false; } return true; } bool objbinsave(const char *path, const ObjData &data) { FILE *pFile = boost::nowide::fopen(path, "wb"); if (pFile == 0) return false; size_t version = 1; ::fwrite(&version, 1, sizeof(version), pFile); bool result = savevector(pFile, data.coordinates) && savevector(pFile, data.textureCoordinates) && savevector(pFile, data.normals) && savevector(pFile, data.parameters) && savevector(pFile, data.mtllibs) && savevectornameidx(pFile, data.usemtls) && savevectornameidx(pFile, data.objects) && savevectornameidx(pFile, data.groups) && savevector(pFile, data.smoothingGroups) && savevector(pFile, data.vertices); ::fclose(pFile); return result; } bool objbinload(const char *path, ObjData &data) { FILE *pFile = boost::nowide::fopen(path, "rb"); if (pFile == 0) return false; data.version = 0; if (::fread(&data.version, sizeof(data.version), 1, pFile) != 1) return false; if (data.version != 1) return false; bool result = loadvector(pFile, data.coordinates) && loadvector(pFile, data.textureCoordinates) && loadvector(pFile, data.normals) && loadvector(pFile, data.parameters) && loadvector(pFile, data.mtllibs) && loadvectornameidx(pFile, data.usemtls) && loadvectornameidx(pFile, data.objects) && loadvectornameidx(pFile, data.groups) && loadvector(pFile, data.smoothingGroups) && loadvector(pFile, data.vertices); ::fclose(pFile); return result; } template bool vectorequal(const std::vector &v1, const std::vector &v2) { if (v1.size() != v2.size()) return false; for (size_t i = 0; i < v1.size(); ++ i) if (! (v1[i] == v2[i])) return false; return true; } bool vectorequal(const std::vector &v1, const std::vector &v2) { if (v1.size() != v2.size()) return false; for (size_t i = 0; i < v1.size(); ++ i) if (v1[i].compare(v2[i]) != 0) return false; return true; } extern bool objequal(const ObjData &data1, const ObjData &data2) { //FIXME ignore version number // version; return vectorequal(data1.coordinates, data2.coordinates) && vectorequal(data1.textureCoordinates, data2.textureCoordinates) && vectorequal(data1.normals, data2.normals) && vectorequal(data1.parameters, data2.parameters) && vectorequal(data1.mtllibs, data2.mtllibs) && vectorequal(data1.usemtls, data2.usemtls) && vectorequal(data1.objects, data2.objects) && vectorequal(data1.groups, data2.groups) && vectorequal(data1.vertices, data2.vertices); } } // namespace ObjParser