GeometryDataLoadStoreOBJ.cc 15.6 KB
Newer Older
1 2 3 4 5 6 7 8
/***********************************************************************
 * Copyright 2011-2012 Computer Graphics Group RWTH Aachen University. *
 * All rights reserved.                                                *
 * Distributed under the terms of the MIT License (see LICENSE.TXT).   *
 **********************************************************************/

#include <ACGL/OpenGL/Data/GeometryDataLoadStore.hh>
#include <ACGL/Math/Math.hh>
9
#include <ACGL/Utils/StringHelpers.hh>
10
#include <ACGL/Utils/MemoryMappedFile.hh>
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

#include <fstream>
#include <string>

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;
    };

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
    // Really naive implementtion of atof but fast
    // if parsing fails std::atof is used the format we expect is a float with a . as decimal point
    float fastAtof(const char * _begin ,const char* _end) {
        const char* p = _begin;
        float r = 0.0;
        bool neg = false;
        if (*p == '-') {
            neg = true;
            ++p;
        }
        while (*p >= '0' && *p <= '9' && p <= _end) {
            r = (r*10.0) + (*p - '0');
            ++p;
        }
        if (*p == '.'  && p <= _end) {
            float f = 0.0;
            int n = 0;
            ++p;
            while (*p >= '0' && *p <= '9'  && p <= _end) {
                f = (f*10.0) + (*p - '0');
                ++p;
                ++n;
            }
            r += f / std::pow(10.0, n);
        }
        if (neg) {
            r = -r;
        }
        if(p < _end)    //we didnt reach the end something went wrong
            return std::atof(_begin);
        return r;
    }

Martin Schultz's avatar
Martin Schultz committed
68 69 70 71 72 73 74 75
    void trim(const char*& _position)
    {
        while(*_position == ' ' || *_position == '\t')
            _position ++;
    }

    const char * nextObject(const char* _position, const char* _end)
    {
Martin Schultz's avatar
Martin Schultz committed
76
        while(_position < _end)
Martin Schultz's avatar
Martin Schultz committed
77
        {
Martin Schultz's avatar
Martin Schultz committed
78 79 80
            if(*_position == ' ' || *_position == '\t')
                return _position;
            ++_position;
Martin Schultz's avatar
Martin Schultz committed
81
        }
Martin Schultz's avatar
Martin Schultz committed
82
        return _position;
Martin Schultz's avatar
Martin Schultz committed
83 84
    }

85
    // Parses a string of space-separated numbers into a packed floating-point vector (_data) with a maximum number of _maxDimension elements
Martin Schultz's avatar
Martin Schultz committed
86
    void parseVector(const char* _it , const char* _end, int _maxDimension, int& _dimension, float* _data)
87
    {
88
        const char* found;
89
        _dimension = 0;
Martin Schultz's avatar
Martin Schultz committed
90
        while (_dimension < _maxDimension && _it < _end)
91
        {
Martin Schultz's avatar
Martin Schultz committed
92 93 94 95
            trim(_it);
            found = nextObject(_it,_end);
            _data[_dimension++] = fastAtof(_it,found-1);
            _it = found == _end ? _end : found + 1;
96 97 98 99 100
        }
    }

    // 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) }
101
    std::vector<IndexTuple> parseIndices(const char* _start, const char* _end)
102 103
    {
        std::vector<IndexTuple> indices;
104
        indices.reserve(5);
105

106
        const char* it = _start;
Martin Schultz's avatar
Martin Schultz committed
107
        trim(it);
108 109 110 111 112 113
        const char* vsit;
        const char* vsend;
        const char* foundSlash;
        int componentIndex;
        int index;
        while (it < _end)
114
        {
115
            vsit = it;
Martin Schultz's avatar
Martin Schultz committed
116
            vsend = nextObject(it, _end);
117
            componentIndex = 0;
118
            IndexTuple indexTuple;
119 120
            //process the string now meaning we split by /
            while (vsit < vsend)
121
            {
Martin Schultz's avatar
Martin Schultz committed
122
                trim(vsit);
123 124
                foundSlash = std::find(vsit, vsend, '/');
                index = std::atoi(vsit);
125 126 127
                if (componentIndex == 0) indexTuple.position = index - 1;
                if (componentIndex == 1) indexTuple.texCoord = index - 1;
                if (componentIndex == 2) indexTuple.normal = index - 1;
128 129
                componentIndex++;
                vsit = foundSlash == vsend ? vsend : foundSlash + 1;
130
            }
131
            indices.push_back(indexTuple);
Martin Schultz's avatar
Martin Schultz committed
132 133
            trim(vsend);
            it = vsend;
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
        }
        return indices;
    }
}

namespace ACGL{
namespace OpenGL{

///////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                           library specific load
///////////////////////////////////////////////////////////////////////////////////////////////////

SharedGeometryData loadGeometryDataFromOBJ(const std::string& _filename, bool _computeNormals)
{
    SharedGeometryData data;
149 150
    MemoryMappedFile mmf(_filename.c_str());
    if(mmf.errorCode())
151 152 153 154
    {
        error() << "could not open file " << _filename << std::endl;
        return data;
    }
155 156
    const char* pchBuf = mmf.data();
    const char* pchEnd = pchBuf + mmf.length();
157 158 159 160 161 162 163 164 165 166 167 168 169 170

    GLenum primitiveType = GL_INVALID_ENUM;
    bool hasTexCoords = false;
    bool hasNormals = false;
    int positionDimension = 4;
    int texCoordDimension = -1;
    int normalDimension = 3;

    std::vector<glm::vec4> positionData;
    std::vector<glm::vec3> texCoordData;
    std::vector<glm::vec3> normalData;

    std::vector<IndexTuple> indices;

171 172
    const char* keyword;
    size_t keywordLength;
173 174
    const char* parameters[2];
    while (pchBuf < pchEnd)
175
    {
Martin Schultz's avatar
Martin Schultz committed
176
        trim(pchBuf);
177
        // Parse the current line
178
        const char* pchEOL = std::find(pchBuf, pchEnd, '\n');
179

Martin Schultz's avatar
Martin Schultz committed
180 181
        // skip empty lines or lines starting with #
        if (*pchBuf == '#' || pchBuf == pchEOL)
182
        {
183
            pchBuf = pchEOL + 1;
184 185 186 187
            continue;
        }

        // Otherwise, extract the first word and the remainder
Martin Schultz's avatar
Martin Schultz committed
188
        const char* pchKey = nextObject(pchBuf, pchEnd);
189
        keyword = pchBuf;
Martin Schultz's avatar
Martin Schultz committed
190 191 192
        keywordLength = pchKey - pchBuf;
        trim(pchKey);
        parameters[0] = pchKey;
193
        parameters[1] = pchEOL;
194

195
        if(strncmp(keyword,"v",keywordLength) == 0) // vertex position
196 197 198
        {
            glm::vec4 position;
            int dimension;
199
            parseVector(parameters[0], parameters[1], 4, dimension, (float*)&position);
200 201 202 203 204 205 206 207 208 209
            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);
        }
210
        else if (strncmp(keyword, "vt", keywordLength) == 0) // vertex tex coord
211 212 213
        {
            glm::vec3 texCoord;
            int dimension;
214
            parseVector(parameters[0], parameters[1], 3, dimension, (float*)&texCoord);
215 216 217 218 219 220 221 222 223 224 225 226 227

            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);
        }
228
        else if (strncmp(keyword, "vn", keywordLength) == 0) // vertex normal
229 230 231
        {
            glm::vec3 normal;
            int dimension;
232
            parseVector(parameters[0], parameters[1], 3, dimension, (float*)&normal);
233 234 235 236 237 238 239 240 241 242 243

            if(dimension < 3)
            {
                error() << "could not load OBJ: wrong vertex normal dimension" << std::endl;
                return data;
            }

            hasNormals = true;

            normalData.push_back(normal);
        }
244
        else if (strncmp(keyword, "p", keywordLength) == 0) // point
245 246 247 248 249 250 251 252 253
        {
            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;
            }

254
            std::vector<IndexTuple> pointIndices = parseIndices(parameters[0], parameters[1]);
255
            // points are just added in order
256
            for(size_t i = 0; i < pointIndices.size(); ++i)
257 258 259 260
            {
                indices.push_back(pointIndices[i]);
            }
        }
261
        else if (strncmp(keyword, "l", keywordLength) == 0) // line
262 263 264 265 266 267 268 269 270
        {
            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;
            }

271
            std::vector<IndexTuple> lineIndices = parseIndices(parameters[0], parameters[1]);
272
            // add line segments for the line strip defined by the vertices
273
            for(size_t i = 0; i < lineIndices.size() - 1; ++i)
274 275 276 277 278
            {
                indices.push_back(lineIndices[i]);
                indices.push_back(lineIndices[i+1]);
            }
        }
279
        else if (strncmp(keyword, "f", keywordLength) == 0) // face
280 281 282 283 284 285 286 287 288
        {
            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;
            }

289
            std::vector<IndexTuple> faceIndices = parseIndices(parameters[0], parameters[1]);
290
            // triangulate the polygon defined by the indices
291
            for(size_t i = 1; i < faceIndices.size() - 1; ++i)
292 293 294 295 296 297
            {
                indices.push_back(faceIndices[0]);
                indices.push_back(faceIndices[i]);
                indices.push_back(faceIndices[i+1]);
            }
        }
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
        else if (strncmp(keyword, "bevel", keywordLength) == 0      || strncmp(keyword, "bmat", keywordLength) == 0
            || strncmp(keyword, "bsp", keywordLength) == 0          || strncmp(keyword, "bzp", keywordLength) == 0
            || strncmp(keyword, "c_interp", keywordLength) == 0     || strncmp(keyword, "cdc", keywordLength) == 0
            || strncmp(keyword, "cdp", keywordLength) == 0          || strncmp(keyword, "con", keywordLength) == 0
            || strncmp(keyword, "cstype", keywordLength) == 0       || strncmp(keyword, "ctech", keywordLength) == 0
            || strncmp(keyword, "curv", keywordLength) == 0         || strncmp(keyword, "curv2", keywordLength) == 0
            || strncmp(keyword, "d_interp", keywordLength) == 0     || strncmp(keyword, "deg", keywordLength) == 0
            || strncmp(keyword, "end", keywordLength) == 0          || strncmp(keyword, "g", keywordLength) == 0
            || strncmp(keyword, "hole", keywordLength) == 0         || strncmp(keyword, "lod", keywordLength) == 0
            || strncmp(keyword, "maplib", keywordLength) == 0       || strncmp(keyword, "mg", keywordLength) == 0
            || strncmp(keyword, "mtllib", keywordLength) == 0       || strncmp(keyword, "o", keywordLength) == 0
            || strncmp(keyword, "parm", keywordLength) == 0         || strncmp(keyword, "res", keywordLength) == 0
            || strncmp(keyword, "s", keywordLength) == 0            || strncmp(keyword, "scrv", keywordLength) == 0
            || strncmp(keyword, "shadow_obj", keywordLength) == 0   || strncmp(keyword, "sp", keywordLength) == 0
            || strncmp(keyword, "stech", keywordLength) == 0        || strncmp(keyword, "step", keywordLength) == 0
            || strncmp(keyword, "surf", keywordLength) == 0         || strncmp(keyword, "trace_obj", keywordLength) == 0
            || strncmp(keyword, "trim", keywordLength) == 0         || strncmp(keyword, "usemap", keywordLength) == 0
            || strncmp(keyword, "usemtl", keywordLength) == 0       || strncmp(keyword, "vp", keywordLength) == 0)
316 317 318 319
        {
            // part of the OBJ specification (i.e. non-polygonal geometry, object groups, etc.)
            // is not supported and is silently ignored
        }
320 321 322 323
        else
        {
            warning() << "unknown OBJ keyword ignored: " << keyword << std::endl;
        }
324
        pchBuf = pchEOL + 1;
325 326
    }

327 328 329 330 331 332 333 334 335
    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
        {
336
            debug() << "model has no normals, computing face normals" << std::endl;
337 338
            hasNormals = true;
        }
339
    } else if (hasNormals) {
340 341 342
        // if the model has normals defined, no face normals have to get computed
        _computeNormals = false;
    }
343

344 345 346 347 348 349 350
    // 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;
351
    for(size_t i = 0; i < indices.size(); ++i)
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
    {
        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)
        {
370 371 372
            if(_computeNormals)
            {
                size_t triangleIndex = i / 3;
373 374 375
                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];
376 377 378
                glm::vec3 normal = glm::cross(v1 - v0, v2 - v0);
                if (normal != glm::vec3(0))
                    normal = glm::normalize(normal);
379 380 381 382 383 384 385 386 387 388 389
                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;
            }
390 391 392 393
        }
    }

    size_t strideSize = 0;
394
    ArrayBuffer::Attribute attrPosition = { "aPosition", GL_FLOAT, positionDimension, (GLuint)0, GL_FALSE, 0, GL_FALSE };
395 396 397 398 399 400
    strideSize += positionDimension * sizeof(GLfloat);

    data->mAttributes.push_back(attrPosition);

    if(hasTexCoords)
    {
401
        ArrayBuffer::Attribute attrTexCoord  = { "aTexCoord", GL_FLOAT, texCoordDimension, (GLuint)strideSize, GL_FALSE, 0, GL_FALSE };
402 403 404 405 406 407 408
        strideSize += texCoordDimension * sizeof(GLfloat);

        data->mAttributes.push_back(attrTexCoord);
    }

    if(hasNormals)
    {
409
        ArrayBuffer::Attribute attrNormal = { "aNormal", GL_FLOAT, normalDimension, (GLuint)strideSize, GL_FALSE, 0, GL_FALSE };
410 411 412 413 414 415 416 417
        strideSize += normalDimension * sizeof(GLfloat);

        data->mAttributes.push_back(attrNormal);
    }

    data->setStrideSize(strideSize);
    data->setSize(abDataElements * sizeof(GLfloat));
    data->setData((GLubyte*)abData);
Martin Schultz's avatar
Martin Schultz committed
418

419 420 421 422 423 424 425 426 427
    return data;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                           library specific save
///////////////////////////////////////////////////////////////////////////////////////////////////

} // OpenGL
} // ACGL