/*********************************************************************** * Copyright 2011-2012 Computer Graphics Group RWTH Aachen University. * * All rights reserved. * * Distributed under the terms of the MIT License (see LICENSE.TXT). * **********************************************************************/ #include #include #include #include #include #include using namespace ACGL; using namespace ACGL::OpenGL; using namespace ACGL::Utils; namespace { struct IndexTuple { IndexTuple() : position(-1), texCoord(-1), normal(-1) { } int position; int texCoord; int normal; }; // Parses a string of space-separated numbers into a packed floating-point vector (_data) with a maximum number of _maxDimension elements void parseVector(const char* _start , const char* _end, int _maxDimension, int& _dimension, float* _data) { const char* it = _start; if (*it == ' ') { it++; } const char* end = _end; const char* found; _dimension = 0; while (_dimension < _maxDimension && it < end) { found = std::find(it, end, ' '); _data[_dimension++] = std::atof(it); it = found == end ? end : found + 1; } } // Turns an index parameter string into a std::vector of IndexTuples, e.g. // "1//2 3//4 5//6" --> { IndexTuple(1, -1, 2), IndexTuple(3, -1, 4), IndexTuple(5, -1, 6) } std::vector parseIndices(const char* _start, const char* _end) { std::vector indices; const char* it = _start; if (*it == ' ') //skip starting whitespace it++; const char* vsit; const char* vsend; const char* foundSlash; std::string vertexString; std::string component; int componentIndex; int index; while (it < _end) { vsit = it; vsend = std::find(it, _end, ' '); componentIndex = 0; IndexTuple indexTuple; indices.push_back(indexTuple); //process the string now meaning we split by / while (vsit < vsend) { foundSlash = std::find(vsit, vsend, '/'); index = std::atoi(vsit); if (componentIndex == 0) indices.back().position = index - 1; if (componentIndex == 1) indices.back().texCoord = index - 1; if (componentIndex == 2) indices.back().normal = index - 1; componentIndex++; vsit = foundSlash == vsend ? vsend : foundSlash + 1; } //indices.push_back(indexTuple); it = vsend == _end ? _end : vsend + 1; } return indices; } } namespace ACGL{ namespace OpenGL{ /////////////////////////////////////////////////////////////////////////////////////////////////// // library specific load /////////////////////////////////////////////////////////////////////////////////////////////////// SharedGeometryData loadGeometryDataFromOBJ(const std::string& _filename, bool _computeNormals) { SharedGeometryData data; MemoryMappedFile mmf(_filename.c_str()); if(mmf.errorCode()) { error() << "could not open file " << _filename << std::endl; return data; } const char* pchBuf = mmf.data(); const char* pchEnd = pchBuf + mmf.length(); GLenum primitiveType = GL_INVALID_ENUM; bool hasTexCoords = false; bool hasNormals = false; int positionDimension = 4; int texCoordDimension = -1; int normalDimension = 3; std::vector positionData; std::vector texCoordData; std::vector normalData; std::vector indices; std::string keyword; const char* parameters[2]; while (pchBuf < pchEnd) { // Parse the current line const char* pchEOL = std::find(pchBuf, pchEnd, '\n'); // If the line starts with a #, it is a comment if (*pchBuf == '#') { pchBuf = pchEOL + 1; continue; } // Otherwise, extract the first word and the remainder const char* pchKey = std::find(pchBuf, pchEnd, ' '); keyword = std::string(pchBuf, pchKey); parameters[0] = pchKey + 1; parameters[1] = pchEOL; if(keyword == "v") // vertex position { glm::vec4 position; int dimension; parseVector(parameters[0], parameters[1], 4, dimension, (float*)&position); if(dimension < 4) position.w = 1.0; if(dimension < 3) { error() << "could not load OBJ: wrong vertex position dimension" << std::endl; return data; } positionData.push_back(position); } else if(keyword == "vt") // vertex tex coord { glm::vec3 texCoord; int dimension; parseVector(parameters[0], parameters[1], 3, dimension, (float*)&texCoord); if(texCoordDimension < 0) texCoordDimension = dimension; else if(texCoordDimension != dimension) { error() << "could not load OBJ: contains mixed tex coord dimensions" << std::endl; return data; } hasTexCoords = true; texCoordData.push_back(texCoord); } else if(keyword == "vn") // vertex normal { glm::vec3 normal; int dimension; parseVector(parameters[0], parameters[1], 3, dimension, (float*)&normal); if(dimension < 3) { error() << "could not load OBJ: wrong vertex normal dimension" << std::endl; return data; } hasNormals = true; normalData.push_back(normal); } else if(keyword == "p") // point { if(primitiveType == GL_INVALID_ENUM) primitiveType = GL_POINTS; else if(primitiveType != GL_POINTS) { error() << "could not load OBJ: contains mixed primitive types" << std::endl; return data; } std::vector pointIndices = parseIndices(parameters[0], parameters[1]); // points are just added in order for(size_t i = 0; i < pointIndices.size(); ++i) { indices.push_back(pointIndices[i]); } } else if(keyword == "l") // line { if(primitiveType == GL_INVALID_ENUM) primitiveType = GL_LINES; else if(primitiveType != GL_LINES) { error() << "could not load OBJ: contains mixed primitive types" << std::endl; return data; } std::vector lineIndices = parseIndices(parameters[0], parameters[1]); // add line segments for the line strip defined by the vertices for(size_t i = 0; i < lineIndices.size() - 1; ++i) { indices.push_back(lineIndices[i]); indices.push_back(lineIndices[i+1]); } } else if(keyword == "f") // face { if(primitiveType == GL_INVALID_ENUM) primitiveType = GL_TRIANGLES; else if(primitiveType != GL_TRIANGLES) { error() << "could not load OBJ: contains mixed primitive types" << std::endl; return data; } std::vector faceIndices = parseIndices(parameters[0], parameters[1]); // triangulate the polygon defined by the indices for(size_t i = 1; i < faceIndices.size() - 1; ++i) { indices.push_back(faceIndices[0]); indices.push_back(faceIndices[i]); indices.push_back(faceIndices[i+1]); } } else if(keyword == "bevel" || keyword == "bmat" || keyword == "bsp" || keyword == "bzp" || keyword == "c_interp" || keyword == "cdc" || keyword == "cdp" || keyword == "con" || keyword == "cstype" || keyword == "ctech" || keyword == "curv" || keyword == "curv2" || keyword == "d_interp" || keyword == "deg" || keyword == "end" || keyword == "g" || keyword == "hole" || keyword == "lod" || keyword == "maplib" || keyword == "mg" || keyword == "mtllib" || keyword == "o" || keyword == "parm" || keyword == "res" || keyword == "s" || keyword == "scrv" || keyword == "shadow_obj" || keyword == "sp" || keyword == "stech" || keyword == "step" || keyword == "surf" || keyword == "trace_obj" || keyword == "trim" || keyword == "usemap" || keyword == "usemtl" || keyword == "vp") { // part of the OBJ specification (i.e. non-polygonal geometry, object groups, etc.) // is not supported and is silently ignored } else { warning() << "unknown OBJ keyword ignored: " << keyword << std::endl; } pchBuf = pchEOL + 1; } if (!hasNormals && _computeNormals) { // perform own per-face normal creation only if the model had no own normals! if(primitiveType != GL_TRIANGLES) { warning() << "computing OBJ normals is only supported for models with faces" << std::endl; _computeNormals = false; } else { debug() << "model has no normals, computing face normals" << std::endl; hasNormals = true; } } else if (hasNormals) { // if the model has normals defined, no face normals have to get computed _computeNormals = false; } // all data are read from the file. construct an ArrayBuffer from the data data = SharedGeometryData(new GeometryData()); size_t abDataElements = (positionDimension + hasTexCoords * texCoordDimension + hasNormals * normalDimension) * indices.size(); GLfloat* abData = new GLfloat[abDataElements]; size_t pos = 0; for(size_t i = 0; i < indices.size(); ++i) { const glm::vec4& position = positionData[indices[i].position]; abData[pos++] = position.x; abData[pos++] = position.y; abData[pos++] = position.z; abData[pos++] = position.w; if(hasTexCoords) { const glm::vec3& texCoord = texCoordData[indices[i].texCoord]; for(int dim = 0; dim < texCoordDimension; ++dim) { abData[pos++] = texCoord[dim]; } } if(hasNormals) { if(_computeNormals) { size_t triangleIndex = i / 3; glm::vec3 v0 = (glm::vec3)positionData[indices[3 * triangleIndex + 0].position]; glm::vec3 v1 = (glm::vec3)positionData[indices[3 * triangleIndex + 1].position]; glm::vec3 v2 = (glm::vec3)positionData[indices[3 * triangleIndex + 2].position]; glm::vec3 normal = glm::cross(v1 - v0, v2 - v0); if (normal != glm::vec3(0)) normal = glm::normalize(normal); abData[pos++] = normal.x; abData[pos++] = normal.y; abData[pos++] = normal.z; } else { const glm::vec3& normal = normalData[indices[i].normal]; abData[pos++] = normal.x; abData[pos++] = normal.y; abData[pos++] = normal.z; } } } size_t strideSize = 0; ArrayBuffer::Attribute attrPosition = { "aPosition", GL_FLOAT, positionDimension, (GLuint)0, GL_FALSE, 0, GL_FALSE }; strideSize += positionDimension * sizeof(GLfloat); data->mAttributes.push_back(attrPosition); if(hasTexCoords) { ArrayBuffer::Attribute attrTexCoord = { "aTexCoord", GL_FLOAT, texCoordDimension, (GLuint)strideSize, GL_FALSE, 0, GL_FALSE }; strideSize += texCoordDimension * sizeof(GLfloat); data->mAttributes.push_back(attrTexCoord); } if(hasNormals) { ArrayBuffer::Attribute attrNormal = { "aNormal", GL_FLOAT, normalDimension, (GLuint)strideSize, GL_FALSE, 0, GL_FALSE }; strideSize += normalDimension * sizeof(GLfloat); data->mAttributes.push_back(attrNormal); } data->setStrideSize(strideSize); data->setSize(abDataElements * sizeof(GLfloat)); data->setData((GLubyte*)abData); std::exit(0); return data; } /////////////////////////////////////////////////////////////////////////////////////////////////// // library specific save /////////////////////////////////////////////////////////////////////////////////////////////////// } // OpenGL } // ACGL