GeometryDataLoadStoreOBJ.cc 13.2 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
35

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

    // Parses a string of space-separated numbers into a packed floating-point vector (_data) with a maximum number of _maxDimension elements
36
    void parseVector(const char* _start , const char* _end, int _maxDimension, int& _dimension, float* _data)
37
    {
38
39
40
41
42
43
44
        const char* it = _start;
        if (*it == ' ')
        {
            it++;
        }
        const char* end = _end;
        const char* found;
45
        _dimension = 0;
46
        while (_dimension < _maxDimension && it < end)
47
        {
48
49
50
            found = std::find(it, end, ' ');
            _data[_dimension++] = std::atof(it);
            it = found == end ? end : found + 1;
51
52
53
54
55
        }
    }

    // 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) }
56
    std::vector<IndexTuple> parseIndices(const char* _start, const char* _end)
57
58
59
    {
        std::vector<IndexTuple> indices;

60
61
62
63
64
65
        const char* it = _start;
        if (*it == ' ') //skip starting whitespace
            it++;
        const char* vsit;
        const char* vsend;
        const char* foundSlash;
66
        std::string vertexString;
67
68
69
70
        std::string component;
        int componentIndex;
        int index;
        while (it < _end)
71
        {
72
73
74
            vsit = it;
            vsend = std::find(it, _end, ' ');
            componentIndex = 0;
75
            IndexTuple indexTuple;
76
77
78
            indices.push_back(indexTuple);
            //process the string now meaning we split by /
            while (vsit < vsend)
79
            {
80
81
82
83
84
85
86
                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;
87
            }
88
89
            //indices.push_back(indexTuple);
            it = vsend == _end ? _end : vsend + 1;
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
        }
        return indices;
    }
}

namespace ACGL{
namespace OpenGL{

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

SharedGeometryData loadGeometryDataFromOBJ(const std::string& _filename, bool _computeNormals)
{
    SharedGeometryData data;
105
106
    MemoryMappedFile mmf(_filename.c_str());
    if(mmf.errorCode())
107
108
109
110
    {
        error() << "could not open file " << _filename << std::endl;
        return data;
    }
111
112
    const char* pchBuf = mmf.data();
    const char* pchEnd = pchBuf + mmf.length();
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

    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;

    std::string keyword;
128
129
    const char* parameters[2];
    while (pchBuf < pchEnd)
130
131
    {
        // Parse the current line
132
        const char* pchEOL = std::find(pchBuf, pchEnd, '\n');
133
134

        // If the line starts with a #, it is a comment
135
        if (*pchBuf == '#')
136
        {
137
            pchBuf = pchEOL + 1;
138
139
140
141
            continue;
        }

        // Otherwise, extract the first word and the remainder
142
143
144
145
        const char* pchKey = std::find(pchBuf, pchEnd, ' ');
        keyword = std::string(pchBuf, pchKey);
        parameters[0] = pchKey + 1;
        parameters[1] = pchEOL;
146
147
148
149
150

        if(keyword == "v") // vertex position
        {
            glm::vec4 position;
            int dimension;
151
            parseVector(parameters[0], parameters[1], 4, dimension, (float*)&position);
152
153
154
155
156
157
158
159
160
161
162
163
164
165
            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;
166
            parseVector(parameters[0], parameters[1], 3, dimension, (float*)&texCoord);
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183

            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;
184
            parseVector(parameters[0], parameters[1], 3, dimension, (float*)&normal);
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205

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

206
            std::vector<IndexTuple> pointIndices = parseIndices(parameters[0], parameters[1]);
207
            // points are just added in order
208
            for(size_t i = 0; i < pointIndices.size(); ++i)
209
210
211
212
213
214
215
216
217
218
219
220
221
222
            {
                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;
            }

223
            std::vector<IndexTuple> lineIndices = parseIndices(parameters[0], parameters[1]);
224
            // add line segments for the line strip defined by the vertices
225
            for(size_t i = 0; i < lineIndices.size() - 1; ++i)
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
            {
                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;
            }

241
            std::vector<IndexTuple> faceIndices = parseIndices(parameters[0], parameters[1]);
242
            // triangulate the polygon defined by the indices
243
            for(size_t i = 1; i < faceIndices.size() - 1; ++i)
244
245
246
247
248
249
            {
                indices.push_back(faceIndices[0]);
                indices.push_back(faceIndices[i]);
                indices.push_back(faceIndices[i+1]);
            }
        }
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
        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
        }
272
273
274
275
        else
        {
            warning() << "unknown OBJ keyword ignored: " << keyword << std::endl;
        }
276
        pchBuf = pchEOL + 1;
277
278
    }

279
280
281
282
283
284
285
286
287
    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
        {
288
            debug() << "model has no normals, computing face normals" << std::endl;
289
290
            hasNormals = true;
        }
291
    } else if (hasNormals) {
292
293
294
        // if the model has normals defined, no face normals have to get computed
        _computeNormals = false;
    }
295

296
297
298
299
300
301
302
    // 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;
303
    for(size_t i = 0; i < indices.size(); ++i)
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
    {
        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)
        {
322
323
324
            if(_computeNormals)
            {
                size_t triangleIndex = i / 3;
325
326
327
                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];
328
329
330
                glm::vec3 normal = glm::cross(v1 - v0, v2 - v0);
                if (normal != glm::vec3(0))
                    normal = glm::normalize(normal);
331
332
333
334
335
336
337
338
339
340
341
                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;
            }
342
343
344
345
        }
    }

    size_t strideSize = 0;
346
    ArrayBuffer::Attribute attrPosition = { "aPosition", GL_FLOAT, positionDimension, (GLuint)0, GL_FALSE, 0, GL_FALSE };
347
348
349
350
351
352
    strideSize += positionDimension * sizeof(GLfloat);

    data->mAttributes.push_back(attrPosition);

    if(hasTexCoords)
    {
353
        ArrayBuffer::Attribute attrTexCoord  = { "aTexCoord", GL_FLOAT, texCoordDimension, (GLuint)strideSize, GL_FALSE, 0, GL_FALSE };
354
355
356
357
358
359
360
        strideSize += texCoordDimension * sizeof(GLfloat);

        data->mAttributes.push_back(attrTexCoord);
    }

    if(hasNormals)
    {
361
        ArrayBuffer::Attribute attrNormal = { "aNormal", GL_FLOAT, normalDimension, (GLuint)strideSize, GL_FALSE, 0, GL_FALSE };
362
363
364
365
366
367
368
369
        strideSize += normalDimension * sizeof(GLfloat);

        data->mAttributes.push_back(attrNormal);
    }

    data->setStrideSize(strideSize);
    data->setSize(abDataElements * sizeof(GLfloat));
    data->setData((GLubyte*)abData);
370
    std::exit(0);
371
372
373
374
375
376
377
378
379
    return data;
}

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

} // OpenGL
} // ACGL