GeometryDataLoadStoreOBJ.cc 15.3 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;
    }

68
    // Parses a string of space-separated numbers into a packed floating-point vector (_data) with a maximum number of _maxDimension elements
69
    void parseVector(const char* _start , const char* _end, int _maxDimension, int& _dimension, float* _data)
70
    {
71
72
73
74
75
76
77
        const char* it = _start;
        if (*it == ' ')
        {
            it++;
        }
        const char* end = _end;
        const char* found;
78
        _dimension = 0;
79
        while (_dimension < _maxDimension && it < end)
80
        {
81
            found = std::find(it, end, ' ');
82
            _data[_dimension++] = fastAtof(it,found-1);
83
            it = found == end ? end : found + 1;
84
85
86
87
88
        }
    }

    // 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) }
89
    std::vector<IndexTuple> parseIndices(const char* _start, const char* _end)
90
91
    {
        std::vector<IndexTuple> indices;
92
        indices.reserve(5);
93

94
95
96
97
98
99
100
101
102
        const char* it = _start;
        if (*it == ' ') //skip starting whitespace
            it++;
        const char* vsit;
        const char* vsend;
        const char* foundSlash;
        int componentIndex;
        int index;
        while (it < _end)
103
        {
104
105
106
            vsit = it;
            vsend = std::find(it, _end, ' ');
            componentIndex = 0;
107
            IndexTuple indexTuple;
108
109
            //process the string now meaning we split by /
            while (vsit < vsend)
110
            {
111
112
                foundSlash = std::find(vsit, vsend, '/');
                index = std::atoi(vsit);
113
114
115
                if (componentIndex == 0) indexTuple.position = index - 1;
                if (componentIndex == 1) indexTuple.texCoord = index - 1;
                if (componentIndex == 2) indexTuple.normal = index - 1;
116
117
                componentIndex++;
                vsit = foundSlash == vsend ? vsend : foundSlash + 1;
118
            }
119
            indices.push_back(indexTuple);
120
            it = vsend == _end ? _end : vsend + 1;
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
        }
        return indices;
    }
}

namespace ACGL{
namespace OpenGL{

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

SharedGeometryData loadGeometryDataFromOBJ(const std::string& _filename, bool _computeNormals)
{
    SharedGeometryData data;
136
137
    MemoryMappedFile mmf(_filename.c_str());
    if(mmf.errorCode())
138
139
140
141
    {
        error() << "could not open file " << _filename << std::endl;
        return data;
    }
142
143
    const char* pchBuf = mmf.data();
    const char* pchEnd = pchBuf + mmf.length();
144
145
146
147
148
149
150
151
152
153
154
155
156
157

    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;

158
159
    const char* keyword;
    size_t keywordLength;
160
161
    const char* parameters[2];
    while (pchBuf < pchEnd)
162
163
    {
        // Parse the current line
164
        const char* pchEOL = std::find(pchBuf, pchEnd, '\n');
165
166

        // If the line starts with a #, it is a comment
167
        if (*pchBuf == '#')
168
        {
169
            pchBuf = pchEOL + 1;
170
171
172
173
            continue;
        }

        // Otherwise, extract the first word and the remainder
174
        const char* pchKey = std::find(pchBuf, pchEnd, ' ');
175
176
        keyword = pchBuf;
        keywordLength = pchKey - pchBuf;//std::string(pchBuf, pchKey);
177
178
        parameters[0] = pchKey + 1;
        parameters[1] = pchEOL;
179

180
        if(strncmp(keyword,"v",keywordLength) == 0) // vertex position
181
182
183
        {
            glm::vec4 position;
            int dimension;
184
            parseVector(parameters[0], parameters[1], 4, dimension, (float*)&position);
185
186
187
188
189
190
191
192
193
194
            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);
        }
195
        else if (strncmp(keyword, "vt", keywordLength) == 0) // vertex tex coord
196
197
198
        {
            glm::vec3 texCoord;
            int dimension;
199
            parseVector(parameters[0], parameters[1], 3, dimension, (float*)&texCoord);
200
201
202
203
204
205
206
207
208
209
210
211
212

            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);
        }
213
        else if (strncmp(keyword, "vn", keywordLength) == 0) // vertex normal
214
215
216
        {
            glm::vec3 normal;
            int dimension;
217
            parseVector(parameters[0], parameters[1], 3, dimension, (float*)&normal);
218
219
220
221
222
223
224
225
226
227
228

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

            hasNormals = true;

            normalData.push_back(normal);
        }
229
        else if (strncmp(keyword, "p", keywordLength) == 0) // point
230
231
232
233
234
235
236
237
238
        {
            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;
            }

239
            std::vector<IndexTuple> pointIndices = parseIndices(parameters[0], parameters[1]);
240
            // points are just added in order
241
            for(size_t i = 0; i < pointIndices.size(); ++i)
242
243
244
245
            {
                indices.push_back(pointIndices[i]);
            }
        }
246
        else if (strncmp(keyword, "l", keywordLength) == 0) // line
247
248
249
250
251
252
253
254
255
        {
            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;
            }

256
            std::vector<IndexTuple> lineIndices = parseIndices(parameters[0], parameters[1]);
257
            // add line segments for the line strip defined by the vertices
258
            for(size_t i = 0; i < lineIndices.size() - 1; ++i)
259
260
261
262
263
            {
                indices.push_back(lineIndices[i]);
                indices.push_back(lineIndices[i+1]);
            }
        }
264
        else if (strncmp(keyword, "f", keywordLength) == 0) // face
265
266
267
268
269
270
271
272
273
        {
            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;
            }

274
            std::vector<IndexTuple> faceIndices = parseIndices(parameters[0], parameters[1]);
275
            // triangulate the polygon defined by the indices
276
            for(size_t i = 1; i < faceIndices.size() - 1; ++i)
277
278
279
280
281
282
            {
                indices.push_back(faceIndices[0]);
                indices.push_back(faceIndices[i]);
                indices.push_back(faceIndices[i+1]);
            }
        }
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
        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)
301
302
303
304
        {
            // part of the OBJ specification (i.e. non-polygonal geometry, object groups, etc.)
            // is not supported and is silently ignored
        }
305
306
307
308
        else
        {
            warning() << "unknown OBJ keyword ignored: " << keyword << std::endl;
        }
309
        pchBuf = pchEOL + 1;
310
311
    }

312
313
314
315
316
317
318
319
320
    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
        {
321
            debug() << "model has no normals, computing face normals" << std::endl;
322
323
            hasNormals = true;
        }
324
    } else if (hasNormals) {
325
326
327
        // if the model has normals defined, no face normals have to get computed
        _computeNormals = false;
    }
328

329
330
331
332
333
334
335
    // 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;
336
    for(size_t i = 0; i < indices.size(); ++i)
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
    {
        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)
        {
355
356
357
            if(_computeNormals)
            {
                size_t triangleIndex = i / 3;
358
359
360
                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];
361
362
363
                glm::vec3 normal = glm::cross(v1 - v0, v2 - v0);
                if (normal != glm::vec3(0))
                    normal = glm::normalize(normal);
364
365
366
367
368
369
370
371
372
373
374
                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;
            }
375
376
377
378
        }
    }

    size_t strideSize = 0;
379
    ArrayBuffer::Attribute attrPosition = { "aPosition", GL_FLOAT, positionDimension, (GLuint)0, GL_FALSE, 0, GL_FALSE };
380
381
382
383
384
385
    strideSize += positionDimension * sizeof(GLfloat);

    data->mAttributes.push_back(attrPosition);

    if(hasTexCoords)
    {
386
        ArrayBuffer::Attribute attrTexCoord  = { "aTexCoord", GL_FLOAT, texCoordDimension, (GLuint)strideSize, GL_FALSE, 0, GL_FALSE };
387
388
389
390
391
392
393
        strideSize += texCoordDimension * sizeof(GLfloat);

        data->mAttributes.push_back(attrTexCoord);
    }

    if(hasNormals)
    {
394
        ArrayBuffer::Attribute attrNormal = { "aNormal", GL_FLOAT, normalDimension, (GLuint)strideSize, GL_FALSE, 0, GL_FALSE };
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
        strideSize += normalDimension * sizeof(GLfloat);

        data->mAttributes.push_back(attrNormal);
    }

    data->setStrideSize(strideSize);
    data->setSize(abDataElements * sizeof(GLfloat));
    data->setData((GLubyte*)abData);
    return data;
}

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

} // OpenGL
} // ACGL