• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Stellarium / stellarium / 3996069357

pending completion
3996069357

push

github

Ruslan Kabatsayev
Shorten some lines

5 of 5 new or added lines in 1 file covered. (100.0%)

14663 of 124076 relevant lines covered (11.82%)

22035.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/core/StelOBJ.cpp
1
/*
2
 * Stellarium
3
 * Copyright (C) 2012 Andrei Borza
4
 * Copyright (C) 2016 Florian Schaukowitsch
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 2
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
19
 */
20

21
#include "StelApp.hpp"
22
#include "StelOBJ.hpp"
23
#include "StelTextureMgr.hpp"
24
#include "StelUtils.hpp"
25

26
#include <QBuffer>
27
#include <QDir>
28
#include <QElapsedTimer>
29
#include <QFile>
30
#include <QFileInfo>
31
#include <QRegularExpression>
32
#include <QTextStream>
33

34
Q_LOGGING_CATEGORY(stelOBJ,"stel.OBJ")
×
35

36
StelOBJ::StelOBJ()
×
37
        : m_isLoaded(false)
×
38
{
39
}
×
40

41
StelOBJ::~StelOBJ()
×
42
{
43
}
×
44

45
void StelOBJ::clear()
×
46
{
47
        //just create a new object
48
        *this = StelOBJ();
×
49
}
×
50

51
bool StelOBJ::load(const QString& filename, const VertexOrder vertexOrder)
×
52
{
53
        qCDebug(stelOBJ)<<"Loading"<<filename;
×
54

55
        QElapsedTimer timer;
×
56
        timer.start();
×
57

58
        //construct base path
59
        QFileInfo fi(filename);
×
60

61
        //try to open the file
62
        QFile file(filename);
×
63
        if(!file.open(QIODevice::ReadOnly))
×
64
        {
65
                qCCritical(stelOBJ)<<"Could not open file"<<filename<<file.errorString();
×
66
                return false;
×
67
        }
68

69
        qCDebug(stelOBJ)<<"Opened file in"<<timer.restart()<<"ms";
×
70

71
        //check if this is a compressed file
72
        if(filename.endsWith(".gz"))
×
73
        {
74
                //uncompress into memory
75
                QByteArray data = StelUtils::uncompress(file);
×
76
                file.close();
×
77
                //check if decompressing was successful
78
                if(data.isEmpty())
×
79
                {
80
                        qCCritical(stelOBJ)<<"Could not decompress file"<<filename;
×
81
                        return false;
×
82
                }
83
                qCDebug(stelOBJ)<<"Decompressed in"<<timer.elapsed()<<"ms";
×
84

85
                //create and open a QBuffer for reading
86
                QBuffer buf(&data);
×
87
                buf.open(QIODevice::ReadOnly);
×
88

89
                //perform actual load
90
                return load(buf,fi.canonicalPath(),vertexOrder);
×
91
        }
×
92

93
        //perform actual load
94
        return load(file,fi.canonicalPath(),vertexOrder);
×
95
}
×
96

97
//macro to test out different ways of comparison and their performance
98
#define CMD_CMP(a) (QLatin1String(a)==cmd)
99

100
//macro to increase a list by size one and return a reference to the last element
101
//used instead of append() to avoid memory copies
102
#define INC_LIST(a) (a.resize(a.size()+1), a.last())
103

104
bool StelOBJ::parseBool(const ParseParams &params, bool &out, int paramsStart)
×
105
{
106
        if(params.size()-paramsStart<1)
×
107
        {
108
                qCCritical(stelOBJ)<<"Expected parameter for statement"<<params;
×
109
                return false;
×
110
        }
111
        if(params.size()-paramsStart>1)
×
112
        {
113
                qCWarning(stelOBJ)<<"Additional parameters ignored in statement"<<params;
×
114
        }
115

116

117
        const ParseParam& cmd = params.at(paramsStart);
×
118
        out = (CMD_CMP("1") || CMD_CMP("true") || CMD_CMP("TRUE") || CMD_CMP("yes") || CMD_CMP("YES"));
×
119

120
        return true;
×
121
}
122

123
bool StelOBJ::parseInt(const ParseParams &params, int &out, int paramsStart)
×
124
{
125
        if(params.size()-paramsStart<1)
×
126
        {
127
                qCCritical(stelOBJ)<<"Expected parameter for statement"<<params;
×
128
                out=0;
×
129
                return false;
×
130
        }
131
        if(params.size()-paramsStart>1)
×
132
        {
133
                qCWarning(stelOBJ)<<"Additional parameters ignored in statement"<<params;
×
134
        }
135

136
        bool ok;
137
        out = params.at(paramsStart).toInt(&ok);
×
138
        return ok;
×
139
}
140

141
bool StelOBJ::parseString(const ParseParams &params, QString &out, int paramsStart)
142
{
143
        if(params.size()-paramsStart<1)
144
        {
145
                qCCritical(stelOBJ)<<"Expected parameter for statement"<<params;
146
                return false;
147
        }
148
        if(params.size()-paramsStart>1)
149
        {
150
                qCWarning(stelOBJ)<<"Additional parameters ignored in statement"<<params;
151
        }
152

153
        out = params.at(paramsStart).toString();
154
        return true;
155
}
156

157
QString StelOBJ::getRestOfString(const QString &strip, const QString &line)
×
158
{
159
        return line.mid(strip.length()).trimmed();
×
160
}
161

162
bool StelOBJ::parseFloat(const ParseParams &params, float &out, int paramsStart)
×
163
{
164
        if(params.size()-paramsStart<1)
×
165
        {
166
                qCCritical(stelOBJ)<<"Expected parameter for statement"<<params;
×
167
                return false;
×
168
        }
169
        if(params.size()-paramsStart>1)
×
170
        {
171
                qCWarning(stelOBJ)<<"Additional parameters ignored in statement"<<params;
×
172
        }
173

174
        bool ok;
175
        out = params.at(paramsStart).toFloat(&ok);
×
176
        return ok;
×
177
}
178

179
template <typename T>
180
bool StelOBJ::parseVec3(const ParseParams& params, T &out, int paramsStart)
×
181
{
182
        if(params.size()-paramsStart<3)
×
183
        {
184
                qCCritical(stelOBJ)<<"Invalid Vec3f specification"<<params;
×
185
                return false;
×
186
        }
187

188
        bool ok = false;
×
189
        out[0] = params.at(paramsStart).toFloat(&ok); //use double here, so that it even works for Vec3d, etc
×
190
        if(ok)
×
191
        {
192
                out[1] = params.at(paramsStart+1).toFloat(&ok);
×
193
                if(ok)
×
194
                {
195
                        out[2] = params.at(paramsStart+2).toFloat(&ok);
×
196
                        return true;
×
197
                }
198
        }
199

200
        qCCritical(stelOBJ)<<"Error parsing Vec3:"<<params;
×
201
        return false;
×
202
}
203

204
template <typename T>
205
bool StelOBJ::parseVec2(const ParseParams& params,T &out, int paramsStart)
×
206
{
207
        if(params.size()-paramsStart<2)
×
208
        {
209
                qCCritical(stelOBJ)<<"Invalid Vec2f specification"<<params;
×
210
                return false;
×
211
        }
212

213
        bool ok = false;
×
214
        out[0] = params.at(paramsStart).toDouble(&ok);
×
215
        if(ok)
×
216
        {
217
                out[1] = params.at(paramsStart+1).toDouble(&ok);
×
218
                return true;
×
219
        }
220

221
        qCCritical(stelOBJ)<<"Error parsing Vec2:"<<params;
×
222
        return false;
×
223
}
224

225
StelOBJ::Object* StelOBJ::getCurrentObject(CurrentParserState &state)
×
226
{
227
        //if there is a current object, return this one
228
        if(state.currentObject)
×
229
                return state.currentObject;
×
230

231
        //create the default object
232
        Object& obj = INC_LIST(m_objects);
×
233
        obj.name = "<default object>";
×
234
        obj.isDefaultObject = true;
×
235
        m_objectMap.insert(obj.name, m_objects.size()-1);
×
236
        state.currentObject = &obj;
×
237
        return &obj;
×
238
}
239

240
StelOBJ::MaterialGroup* StelOBJ::getCurrentMaterialGroup(CurrentParserState &state)
×
241
{
242
        int matIdx = getCurrentMaterialIndex(state);
×
243
        //if there is a current material group, check if a new one must be created because the material changed
244
        if(state.currentMaterialGroup && state.currentMaterialGroup->materialIndex==matIdx)
×
245
                return state.currentMaterialGroup;
×
246

247
        //no material group has been created yet
248
        //or the material has changed
249
        //we need to create a new group
250
        //we need an object for this
251
        Object* curObj = getCurrentObject(state);
×
252

253
        MaterialGroup& grp = INC_LIST(curObj->groups);
×
254
        grp.materialIndex = matIdx;
×
255
        //the object should always be the most recently added one
256
        grp.objectIndex = m_objects.size()-1;
×
257
        //the start index is positioned after the end of the index list
258
        grp.startIndex = m_indices.size();
×
259
        state.currentMaterialGroup = &grp;
×
260
        return &grp;
×
261
}
262

263
int StelOBJ::getCurrentMaterialIndex(CurrentParserState &state)
×
264
{
265
        //if there has been a material definition before, we use this
266
        if(m_materials.size())
×
267
                return state.currentMaterialIdx;
×
268

269
        //only if no material has been defined before any face,
270
        //we need to create a default material
271
        //this is "a white material" according to http://paulbourke.net/dataformats/obj/
272
        Material& mat = INC_LIST(m_materials);
×
273
        mat.name = "<default material>";
×
274
        mat.Kd = QVector3D(0.8f, 0.8f, 0.8f);
×
275
        mat.Ka = QVector3D(0.1f, 0.1f, 0.1f);
×
276

277
        m_materialMap.insert(mat.name, m_materials.size()-1);
×
278
        state.currentMaterialIdx = 0;
×
279
        return 0;
×
280
}
281

282
bool StelOBJ::parseFace(const ParseParams& params, const V3Vec& posList, const V3Vec& normList, const V2Vec& texList,
×
283
                        CurrentParserState& state,
284
                        VertexCache& vertCache)
285
{
286
        //The face definition can have 4 different variants
287
        //Mode 1: Only position:                f v1 v2 v3
288
        //Mode 2: Position+texcoords:                f v1/t1 v2/t2 v3/t3
289
        //Mode 3: Position+texcoords+normals:        f v1/t1/n1 v2/t2/n2 v3/t3/n3
290
        //Mode 4: Position+normals:                f v1//n1 v2//n2 v3//n3
291

292
        // Zero is actually invalid in the face definition, so we use it for default values
293
        int posIdx=0, texIdx=0, normIdx=0;
×
294
        // Contains the vertex indices
295
        QVarLengthArray<unsigned int,16> vIdx;
×
296

297
        if(params.size()<4)
×
298
        {
299
                qCCritical(stelOBJ)<<"Invalid number of vertices in face statement"<<params;
×
300
                return false;
×
301
        }
302

303
        int vtxAmount = params.size()-1;
×
304

305
        //parse each one separately
306
        int mode = 0;
×
307
        bool ok = false;
×
308
        //a macro for consistency check
309
        #define CHK_MODE(a) if(mode && mode!=a) { qCCritical(stelOBJ)<<"Inconsistent face statement"<<params; return false; } else {mode = a;}
310
        //a macro for checking number parsing
311
        #define CHK_OK(a) do{ a; if(!ok) { qCCritical(stelOBJ)<<"Could not parse number in face statement"<<params; return false; } } while(0)
312
        //negative indices indicate relative data, i.e. -1 would mean the last position/texture/normal that was parsed
313
        //this macro fixes it up so that it always uses absolute numbers
314
        //note: the indices start with 1, this is fixed up later
315
        #define FIX_REL(a, list) if(a<0) {a += list.size()+1; }
316

317
        //loop to parse each section separately
318
        for(int i =0; i<vtxAmount;++i)
×
319
        {
320
                //split on slash
321
                #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
322
                ParseParams split = params.at(i+1).split('/').toVector();
323
                #else
324
                ParseParams split = params.at(i+1).split('/');
×
325
                #endif
326
                switch(split.size())
×
327
                {
328
                        case 1: //no slash, only position
×
329
                                CHK_MODE(1)
×
330
                                CHK_OK(posIdx = split.at(0).toInt(&ok));
×
331
                                FIX_REL(posIdx, posList)
×
332
                                break;
×
333
                        case 2: //single slash, vert/tex
×
334
                                CHK_MODE(2)
×
335
                                CHK_OK(posIdx = split.at(0).toInt(&ok));
×
336
                                FIX_REL(posIdx, posList)
×
337
                                CHK_OK(texIdx = split.at(1).toInt(&ok));
×
338
                                FIX_REL(texIdx, texList)
×
339
                                break;
×
340
                        case 3: //2 slashes, either v/t/n or v//n
×
341
                                if(!split.at(1).isEmpty())
×
342
                                {
343
                                        CHK_MODE(3)
×
344
                                        CHK_OK(posIdx = split.at(0).toInt(&ok));
×
345
                                        FIX_REL(posIdx, posList)
×
346
                                        CHK_OK(texIdx = split.at(1).toInt(&ok));
×
347
                                        FIX_REL(texIdx, texList)
×
348
                                        CHK_OK(normIdx = split.at(2).toInt(&ok));
×
349
                                        FIX_REL(normIdx, normList)
×
350
                                }
351
                                else
352
                                {
353
                                        CHK_MODE(4)
×
354
                                        CHK_OK(posIdx = split.at(0).toInt(&ok));
×
355
                                        FIX_REL(posIdx, posList)
×
356
                                        CHK_OK(normIdx = split.at(2).toInt(&ok));
×
357
                                        FIX_REL(normIdx, normList)
×
358
                                }
359
                                break;
×
360
                        default: //invalid line
×
361
                                qCCritical(stelOBJ)<<"Invalid face statement"<<params;
×
362
                                return false;
×
363
                }
364

365
                //create a temporary Vertex by copying the info from the lists
366
                //zero initialize!
367
                Vertex v = Vertex();
×
368
                if(posIdx)
×
369
                {
370
                        const float* data = posList.at(posIdx-1).v;
×
371
                        std::copy(data, data+3, v.position);
×
372
                }
373
                if(texIdx)
×
374
                {
375
                        const float* data = texList.at(texIdx-1).v;
×
376
                        std::copy(data, data+2, v.texCoord);
×
377
                }
378
                if(normIdx)
×
379
                {
380
                        const float* data = normList.at(normIdx-1).v;
×
381
                        std::copy(data, data+3, v.normal);
×
382
                }
383

384
                //check if the vertex is already in the vertex cache
385
                auto it = vertCache.find(v);
×
386
                if(it!=vertCache.end())
×
387
                {
388
                        //cache hit, reuse index
389
                        vIdx.append(*it);
×
390
                }
391
                else
392
                {
393
                        //vertex unknown, add it to the vertex list and cache
394
                        unsigned int idx = static_cast<unsigned int>(m_vertices.size());
×
395
                        vertCache.insert(v,m_vertices.size());
×
396
                        m_vertices.append(v);
×
397
                        vIdx.append(idx);
×
398
                }
399
        }
×
400

401
        //get/create current material group
402
        MaterialGroup* grp = getCurrentMaterialGroup(state);
×
403

404
        //vertex data has been loaded, create the faces
405
        //we use triangle-fan triangulation
406
        for(int i=2;i<vtxAmount;++i)
×
407
        {
408
                //the first one is always the same
409
                m_indices.append(vIdx[0]);
×
410
                m_indices.append(vIdx[i-1]);
×
411
                m_indices.append(vIdx[i]);
×
412
                //add the triangle to the group
413
                grp->indexCount+=3;
×
414
        }
415

416
        return true;
×
417
}
×
418

419
StelOBJ::MaterialList StelOBJ::Material::loadFromFile(const QString &filename)
×
420
{
421
        StelOBJ::MaterialList list;
×
422

423
        QFileInfo fi(filename);
×
424
        QDir dir = fi.dir();
×
425
        QFile file(filename);
×
426
        if(!file.open(QIODevice::ReadOnly))
×
427
        {
428
                qCWarning(stelOBJ)<<"Could not open MTL file"<<filename<<file.errorString();
×
429
                return list;
×
430
        }
431

432
        QTextStream stream(&file);
×
433
        Material* curMaterial = Q_NULLPTR;
×
434
        int lineNr = 0;
×
435
        // some exporters give d and Tr, and some give contradicting interpretations. Track a warning with these.
436
        bool dHasBeenGiven = false;
×
437
        bool trHasBeenGiven = false;
×
438

439
        while(!stream.atEnd())
×
440
        {
441
                ++lineNr;
×
442
                bool ok = true;
×
443
                //make sure only spaces are the separator
444
                QString line = stream.readLine().simplified();
×
445
                //split line by space                
446
                #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
447
                ParseParams splits = ParseParam(line).split(' ', Qt::SkipEmptyParts).toVector();
448
                #elif (QT_VERSION>=QT_VERSION_CHECK(5,15,0))
449
                ParseParams splits = line.splitRef(' ', Qt::SkipEmptyParts);
×
450
                #else
451
                ParseParams splits = line.splitRef(' ', QString::SkipEmptyParts);
452
                #endif
453
                if(!splits.isEmpty())
×
454
                {
455
                        const ParseParam& cmd = splits.at(0);
×
456

457
                        //macro to make sure a material is currently active
458
                        #define CHECK_MTL() if(!curMaterial) { ok = false; qCCritical(stelOBJ)<<"Encountered material statement without active material"; }
459
                        //macro to make path absolute, also to force use of forward slashes
460
                        #define MAKE_ABS(a) if(!a.isEmpty()){ a = dir.absoluteFilePath(QDir::cleanPath(a.replace('\\','/'))); }
461
                        if(CMD_CMP("newmtl")) //define new material
×
462
                        {
463
                                //use rest of line to support spaces in file name
464
                                QString name = getRestOfString(QStringLiteral("newmtl"),line);
×
465
                                ok = !name.isEmpty();
×
466
                                if(ok)
×
467
                                {
468
                                        //add a new material with the specified name
469
                                        curMaterial = &INC_LIST(list);
×
470
                                        curMaterial->name = name;
×
471
                                        dHasBeenGiven = false;
×
472
                                        trHasBeenGiven = false;
×
473
                                }
474
                                else
475
                                {
476
                                        qCCritical(stelOBJ)<<"Invalid newmtl statement"<<line;
×
477
                                }
478
                        }
×
479
                        else if(CMD_CMP("Ka")) //define ambient color
×
480
                        {
481
                                CHECK_MTL()
×
482
                                if(ok)
×
483
                                        ok = parseVec3(splits,curMaterial->Ka);
×
484
                        }
485
                        else if(CMD_CMP("Kd")) //define diffuse color
×
486
                        {
487
                                CHECK_MTL()
×
488
                                if(ok)
×
489
                                        ok = parseVec3(splits,curMaterial->Kd);
×
490
                        }
491
                        else if(CMD_CMP("Ks")) //define specular color
×
492
                        {
493
                                CHECK_MTL()
×
494
                                if(ok)
×
495
                                        ok = parseVec3(splits,curMaterial->Ks);
×
496
                        }
497
                        else if(CMD_CMP("Ke")) //define emissive color
×
498
                        {
499
                                CHECK_MTL()
×
500
                                if(ok)
×
501
                                        ok = parseVec3(splits,curMaterial->Ke);
×
502
                        }
503
                        else if(CMD_CMP("Ns")) //define specular coefficient
×
504
                        {
505
                                CHECK_MTL()
×
506
                                if(ok)
×
507
                                        ok = StelOBJ::parseFloat(splits,curMaterial->Ns);
×
508
                        }
509
                        else if(CMD_CMP("d"))
×
510
                        {
511
                                CHECK_MTL()
×
512
                                if(ok)
×
513
                                {
514
                                        ok = StelOBJ::parseFloat(splits,curMaterial->d);
×
515
                                        //clamp d to [0,1]
516
                                        curMaterial->d = qBound(0.0f, curMaterial->d, 1.0f);
×
517
                                        if (trHasBeenGiven)
×
518
                                        {
519
                                                qWarning(stelOBJ) << "Material" << curMaterial->name << "warning: Tr and d both given. The latter wins, d=" << curMaterial->d;
×
520
                                        }
521
                                        dHasBeenGiven=true;
×
522
                                }
523
                        }
524
                        else if(CMD_CMP("Tr"))
×
525
                        {
526
                                CHECK_MTL()
×
527
                                if(ok)
×
528
                                {
529
                                        //Tr should be the inverse of d, in theory
530
                                        //not all exporters seem to follow this rule...
531
                                        ok = StelOBJ::parseFloat(splits,curMaterial->d);
×
532
                                        //clamp d to [0,1]
533
                                        curMaterial->d = 1.0f - qBound(0.0f, curMaterial->d, 1.0f);
×
534
                                        if (dHasBeenGiven)
×
535
                                        {
536
                                                qWarning(stelOBJ) << "Material" << curMaterial->name << "warning: d and Tr both given. The latter wins, Tr=" << 1.0f-curMaterial->d;
×
537
                                        }
538
                                        trHasBeenGiven=true;
×
539
                                }
540
                        }
541
                        else if(CMD_CMP("map_Ka")) //define ambient map
×
542
                        {
543
                                CHECK_MTL()
×
544
                                if(ok)
×
545
                                {
546
                                        //use rest of line to support spaces in file name
547
                                        curMaterial->map_Ka = getRestOfString(QStringLiteral("map_Ka"),line);
×
548
                                        ok = !curMaterial->map_Ka.isEmpty();
×
549
                                        MAKE_ABS(curMaterial->map_Ka)
×
550
                                }
551
                        }
552
                        else if(CMD_CMP("map_Kd")) //define diffuse map
×
553
                        {
554
                                CHECK_MTL()
×
555
                                if(ok)
×
556
                                {
557
                                        //use rest of line to support spaces in file name
558
                                        curMaterial->map_Kd = getRestOfString(QStringLiteral("map_Kd"),line);
×
559
                                        ok = !curMaterial->map_Kd.isEmpty();
×
560
                                        MAKE_ABS(curMaterial->map_Kd)
×
561
                                }
562
                        }
563
                        else if(CMD_CMP("map_Ks")) //define specular map
×
564
                        {
565
                                CHECK_MTL()
×
566
                                if(ok)
×
567
                                {
568
                                        //use rest of line to support spaces in file name
569
                                        curMaterial->map_Ks = getRestOfString(QStringLiteral("map_Ks"),line);
×
570
                                        ok = !curMaterial->map_Ks.isEmpty();
×
571
                                        MAKE_ABS(curMaterial->map_Ks)
×
572
                                }
573
                        }
574
                        else if(CMD_CMP("map_Ke")) //define emissive map
×
575
                        {
576
                                CHECK_MTL()
×
577
                                if(ok)
×
578
                                {
579
                                        //use rest of line to support spaces in file name
580
                                        curMaterial->map_Ke = getRestOfString(QStringLiteral("map_Ke"),line);
×
581
                                        ok = !curMaterial->map_Ke.isEmpty();
×
582
                                        MAKE_ABS(curMaterial->map_Ke)
×
583
                                }
584
                        }
585
                        else if(CMD_CMP("map_bump")) //define bump/normal map
×
586
                        {
587
                                CHECK_MTL()
×
588
                                if(ok)
×
589
                                {
590
                                        //use rest of line to support spaces in file name
591
                                        curMaterial->map_bump = getRestOfString(QStringLiteral("map_bump"),line);
×
592
                                        ok = !curMaterial->map_bump.isEmpty();
×
593
                                        MAKE_ABS(curMaterial->map_bump)
×
594
                                }
595
                        }
596
                        else if(CMD_CMP("map_height")) //define height map
×
597
                        {
598
                                CHECK_MTL()
×
599
                                if(ok)
×
600
                                {
601
                                        //use rest of line to support spaces in file name
602
                                        curMaterial->map_height = getRestOfString(QStringLiteral("map_height"),line);
×
603
                                        ok = !curMaterial->map_height.isEmpty();
×
604
                                        MAKE_ABS(curMaterial->map_height)
×
605
                                }
606
                        }
607
                        else if(CMD_CMP("illum"))
×
608
                        {
609
                                CHECK_MTL()
×
610
                                if(ok)
×
611
                                {
612
                                        int tmp;
613
                                        ok = parseInt(splits,tmp);
×
614
                                        curMaterial->illum = static_cast<Illum>(tmp);
×
615

616
                                        if(tmp<I_DIFFUSE || tmp > I_TRANSLUCENT)
×
617
                                        {
618
                                                ok = false;
×
619
                                                tmp = I_NONE;
×
620
                                                qCCritical(stelOBJ())<<"Invalid illum statement"<<line;
×
621
                                        }
622

623
                                        //if between these 2, set to translucent and warn
624
                                        if(tmp>I_SPECULAR && tmp < I_TRANSLUCENT)
×
625
                                        {
626
                                                qCWarning(stelOBJ())<<"Treating illum "<<tmp<<"as TRANSLUCENT";
×
627
                                                tmp = I_TRANSLUCENT;
×
628
                                        }
629
                                        curMaterial->illum = static_cast<Illum>(tmp);
×
630
                                }
631
                        }
632
                        else if(!cmd.startsWith(QChar('#')))
×
633
                        {
634
                                CHECK_MTL()
×
635
                                if(ok)
×
636
                                {
637
                                        //unknown command, add to additional params
638
                                        //we need to convert to actual string instances to store them
639
                                        QStringList list;
×
640
                                        for(int i = 1; i<splits.size();++i)
×
641
                                        {
642
                                                list.append(splits.at(i).toString());
×
643
                                        }
644

645
                                        curMaterial->additionalParams.insert(cmd.toString(),list);
×
646
                                        //qCWarning(stelOBJ)<<"Unknown MTL statement:"<<line;
647
                                }
×
648
                        }
649
                }
650

651
                if(!ok)
×
652
                {
653
                        list.clear();
×
654
                        qCCritical(stelOBJ)<<"Critical error in MTL file"<<filename<<"at line"<<lineNr<<", cannot process: "<<line;
×
655
                        break;
×
656
                }
657
        }
×
658

659
        return list;
×
660
}
×
661

662
bool StelOBJ::Material::parseBool(const QStringList &params, bool &out)
×
663
{
664
        ParseParams pp(params.size());
×
665
        for(int i = 0; i< params.size();++i)
×
666
        {
667
#if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
668
                pp[i] = params.at(i);
669
#else
670
                pp[i] = QStringRef(&params.at(i));
×
671
#endif
672
        }
673
        return StelOBJ::parseBool(pp,out,0);
×
674
}
×
675

676
bool StelOBJ::Material::parseFloat(const QStringList &params, float &out)
×
677
{
678
        ParseParams pp(params.size());
×
679
        for(int i = 0; i< params.size();++i)
×
680
        {
681
#if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
682
                pp[i] = params.at(i);
683
#else
684
                pp[i] = ParseParam(&params.at(i));
×
685
#endif
686
        }
687
        return StelOBJ::parseFloat(pp,out,0);
×
688
}
×
689

690
bool StelOBJ::Material::parseVec2d(const QStringList &params, Vec2d &out)
×
691
{
692
        ParseParams pp(params.size());
×
693
        for(int i = 0; i< params.size();++i)
×
694
        {
695
#if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
696
                pp[i] = params.at(i);
697
#else
698
                pp[i] = ParseParam(&params.at(i));
×
699
#endif
700
        }
701
        return StelOBJ::parseVec2(pp,out,0);
×
702
}
×
703

704
void StelOBJ::addObject(const QString &name, CurrentParserState &state)
×
705
{
706
        //check if the last object contained anything, if not remove it
707
        if(state.currentObject)
×
708
        {
709
                if(state.currentObject->groups.isEmpty())
×
710
                {
711
                        Q_ASSERT(state.currentObject == &m_objects.last());
×
712
                        m_objectMap.remove(state.currentObject->name);
×
713
                        m_objects.removeLast();
×
714
                }
715
        }
716

717
        //create new object
718
        Object& obj = INC_LIST(m_objects);
×
719
        obj.name = name;
×
720
        m_objectMap.insert(obj.name,m_objects.size()-1);
×
721
        state.currentObject = &obj;
×
722
        //also clear material group to make sure a new group is created
723
        state.currentMaterialGroup = Q_NULLPTR;
×
724
}
×
725

726
bool StelOBJ::load(QIODevice& device, const QString &basePath, const VertexOrder vertexOrder)
×
727
{
728
        clear();
×
729

730
        QDir baseDir(basePath);
×
731

732
        QElapsedTimer timer;
×
733
        timer.start();
×
734
        QTextStream stream(&device);
×
735

736
        bool smoothGroupWarned = false;
×
737
        bool vertexWWarned = false;
×
738
        bool textureWWarned = false;
×
739

740
        //contains the parsed vertex positions
741
        V3Vec posList;
×
742
        //contains the parsed normals
743
        V3Vec normalList;
×
744
        //contains the parsed texture coords
745
        V2Vec texList;
×
746

747
        VertexCache vertCache;
×
748
        CurrentParserState state = CurrentParserState();
×
749
        static const QRegularExpression separator("\\s");
×
750
        separator.optimize();
×
751

752
        int lineNr=0;
×
753

754
        //read file line by line
755
        while(!stream.atEnd())
×
756
        {
757
                ++lineNr;
×
758
                //ignore front/back whitespace
759
                QString line = stream.readLine().trimmed();
×
760

761
                //split line by whitespace
762
                #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
763
                ParseParams splits = ParseParam(line).split(separator, Qt::SkipEmptyParts).toVector();
764
                #elif (QT_VERSION>=QT_VERSION_CHECK(5,15,0))
765
                ParseParams splits = line.splitRef(separator, Qt::SkipEmptyParts);
×
766
                #else
767
                ParseParams splits = line.splitRef(separator, QString::SkipEmptyParts);
768
                #endif
769
                if(!splits.isEmpty())
×
770
                {
771
                        const ParseParam& cmd = splits.at(0);
×
772

773
                        bool ok = true;
×
774

775
                        if(CMD_CMP("f"))
×
776
                        {
777
                                ok = parseFace(splits,posList,normalList,texList,state,vertCache);
×
778
                        }
779
                        else if(CMD_CMP("v"))
×
780
                        {
781
                                //we have to handle the vertex order
782
                                Vec3f& target = INC_LIST(posList);
×
783
                                ok = parseVec3(splits,target);
×
784
                                //check the optional w coord if we have a vec4, must be 1
785
                                if(splits.size()>4)
×
786
                                {
787
                                        float w;
788
                                        parseFloat(splits,w,4);
×
789
                                        if((!qFuzzyCompare(w,1.0f)) && (!vertexWWarned))
×
790
                                        {
791
                                                qWarning(stelOBJ)<<"Vertex w coordinates different from 1.0 are not supported, changed to 1.0, starting on line"<<lineNr;
×
792
                                                vertexWWarned=true;
×
793
                                        }
794
                                }
795
                                switch(vertexOrder)
×
796
                                {
797
                                        case XYZ:
×
798
                                                //no change
799
                                                break;
×
800
                                        case XZY:
×
801
                                                target.set(target[0],-target[2],target[1]);
×
802
                                                break;
×
803
                                        case YXZ:
×
804
                                                target.set(target[1],target[0],target[2]);
×
805
                                                break;
×
806
                                        case YZX:
×
807
                                                target.set(target[1],target[2],target[0]);
×
808
                                                break;
×
809
                                        case ZXY:
×
810
                                                target.set(target[2],target[0],target[1]);
×
811
                                                break;
×
812
                                        case ZYX:
×
813
                                                target.set(target[2],target[1],target[0]);
×
814
                                                break;
×
815
                                        default:
×
816
                                                Q_ASSERT_X(0,"StelOBJ::load","invalid vertex order found");
×
817
                                                qCWarning(stelOBJ) << "Vertex order"<<vertexOrder<<"not implemented, assuming XYZ";
818
                                                break;
819
                                }
820
                        }
821
                        else if(CMD_CMP("vt"))
×
822
                        {
823
                                ok = parseVec2(splits,INC_LIST(texList));
×
824
                                //check the optional w coord if we have a vec3, must be 0
825
                                if(splits.size()>3)
×
826
                                {
827
                                        float w;
828
                                        parseFloat(splits,w,3);
×
829
                                        if( (!qFuzzyIsNull(w)) && (!textureWWarned))
×
830
                                        {
831
                                                qWarning(stelOBJ)<<"Texture w coordinates are not supported, starting on line"<<lineNr;
×
832
                                                textureWWarned=true;
×
833
                                        }
834
                                }
835
                        }
836
                        else if(CMD_CMP("vn"))
×
837
                        {
838
                                //we have to handle the vertex order
839
                                Vec3f& target = INC_LIST(normalList);
×
840
                                ok = parseVec3(splits,target);
×
841
                                switch(vertexOrder)
×
842
                                {
843
                                        case XYZ:
×
844
                                                //no change
845
                                                break;
×
846
                                        case XZY:
×
847
                                                target.set(target[0],-target[2],target[1]);
×
848
                                                break;
×
849
                                        case YXZ:
×
850
                                                target.set(target[1],target[0],target[2]);
×
851
                                                break;
×
852
                                        case YZX:
×
853
                                                target.set(target[1],target[2],target[0]);
×
854
                                                break;
×
855
                                        case ZXY:
×
856
                                                target.set(target[2],target[0],target[1]);
×
857
                                                break;
×
858
                                        case ZYX:
×
859
                                                target.set(target[2],target[1],target[0]);
×
860
                                                break;
×
861
                                        default:
×
862
                                                Q_ASSERT_X(0,"StelOBJ::load","invalid vertex order found");
×
863
                                                qCWarning(stelOBJ) << "Vertex order"<<vertexOrder<<"not implemented, assuming XYZ";
864
                                                break;
865
                                }
866
                                //normalize is usually not needed so we skip it
867
                                //target.normalize();
868
                        }
869
                        else if(CMD_CMP("usemtl"))
×
870
                        {
871
                                //use the rest of the string
872
                                QString mtl = getRestOfString(QStringLiteral("usemtl"),line);
×
873
                                ok = !mtl.isEmpty();
×
874
                                if(ok)
×
875
                                {
876
                                        if(m_materialMap.contains(mtl))
×
877
                                        {
878
                                                //set material as active
879
                                                state.currentMaterialIdx = m_materialMap.value(mtl);
×
880
                                        }
881
                                        else
882
                                        {
883
                                                ok = false;
×
884
                                                qCCritical(stelOBJ)<<"Unknown material"<<mtl<<"has been referenced";
×
885
                                        }
886
                                }
887
                                else
888
                                        qCCritical(stelOBJ)<<"No material name given";
×
889
                        }
×
890
                        else if(CMD_CMP("mtllib"))
×
891
                        {
892
                                //use the rest of the string
893
                                QString fileName = getRestOfString(QStringLiteral("mtllib"),line);
×
894
                                ok = !fileName.isEmpty();
×
895
                                if(ok)
×
896
                                {
897
                                        //load external material file
898
                                        const MaterialList newMaterials = Material::loadFromFile(baseDir.absoluteFilePath(fileName));
×
899
                                        for (const auto& m : newMaterials)
×
900
                                        {
901
                                                m_materials.append(m);
×
902
                                                //the map has the index of the material
903
                                                //because pointers may change during parsing
904
                                                //because of list resizeing
905
                                                m_materialMap.insert(m.name,m_materials.size()-1);
×
906
                                        }
907
                                        qCDebug(stelOBJ)<<newMaterials.size()<<"materials loaded from MTL file"<<fileName;
×
908
                                }
×
909
                                else
910
                                        qCCritical(stelOBJ)<<"No material file name given";
×
911
                        }
×
912
                        else if(CMD_CMP("o"))
×
913
                        {
914
                                //use the rest of the string
915
                                QString objName = getRestOfString(QStringLiteral("o"),line);
×
916
                                ok = !objName.isEmpty();
×
917
                                if(ok)
×
918
                                {
919
                                        addObject(objName, state);
×
920
                                }
921
                                else
922
                                        qCCritical(stelOBJ)<<"Object name is required";
×
923
                        }
×
924
                        else if(CMD_CMP("g"))
×
925
                        {
926
                                //use the rest of the string
927
                                QString objName = getRestOfString(QStringLiteral("g"),line);
×
928
                                ok = !objName.isEmpty();
×
929
                                if(ok)
×
930
                                {
931
                                        addObject(objName, state);
×
932
                                }
933
                                else
934
                                        qCCritical(stelOBJ)<<"Group name is required";
×
935
                        }
×
936
                        else if(CMD_CMP("s"))
×
937
                        {
938
                                if(!smoothGroupWarned)
×
939
                                {
940
                                        qCWarning(stelOBJ)<<"Smoothing groups are not supported, consider re-exporting your model from Blender";
×
941
                                        smoothGroupWarned = true;
×
942
                                }
943
                        }
944
                        else if(!cmd.startsWith('#'))
×
945
                        {
946
                                //unknown command, warn
947
                                qCWarning(stelOBJ)<<"Unknown OBJ statement:"<<line;
×
948
                        }
949

950
                        if(!ok)
×
951
                        {
952
                                qCCritical(stelOBJ)<<"Critical error on OBJ line"<<lineNr<<", cannot load OBJ data: "<<line;
×
953
                                return false;
×
954
                        }
955
                }
956
        }
×
957

958
        device.close();
×
959

960
        //finished loading, squeeze the arrays to save some memory
961
        m_vertices.squeeze();
×
962
        m_indices.squeeze();
×
963

964
        Q_ASSERT(m_indices.size() % 3 == 0);
×
965

966
        qCDebug(stelOBJ)<<"Loaded OBJ in"<<timer.elapsed()<<"ms";
×
967
        qCDebug(stelOBJ, "Parsed %d positions, %d normals, %d texture coordinates, %d materials",
×
968
                int(posList.size()), int(normalList.size()), int(texList.size()), int(m_materials.size()));
969
        qCDebug(stelOBJ, "Created %d vertices, %d faces, %d objects", int(m_vertices.size()), getFaceCount(), int(m_objects.size()));
×
970

971
        //perform post processing
972
        performPostProcessing(normalList.isEmpty());
×
973
        m_isLoaded = true;
×
974
        return true;
×
975
}
×
976

977
void StelOBJ::Object::postprocess(const StelOBJ &obj, Vec3d &centroid)
×
978
{
979
        const VertexList& vList = obj.getVertexList();
×
980
        const IndexList& iList = obj.getIndexList();
×
981

982
        int idxCnt = 0;
×
983
        boundingbox.reset();
×
984

985
        //iterate through the groups
986
        for(int i =0;i<groups.size();++i)
×
987
        {
988
                MaterialGroup& grp = groups[i];
×
989
                Vec3d accVertex(0.);
×
990

991
                Q_ASSERT(grp.indexCount > 0);
×
992
                grp.boundingbox.reset();
×
993

994
                //iterate through the vertices of the group
995
                for(int idx = grp.startIndex;idx<(grp.startIndex+grp.indexCount);++idx)
×
996
                {
997
                        const Vertex& v = vList.at(static_cast<int>(iList.at(idx)));
×
998
                        Vec3f pos(v.position);
×
999
                        grp.boundingbox.expand(pos);
×
1000
                        accVertex+=pos.toVec3d();
×
1001
                        centroid+=pos.toVec3d();
×
1002
                }
1003
                boundingbox.expand(grp.boundingbox);
×
1004
                grp.centroid = (accVertex / grp.indexCount).toVec3f();
×
1005

1006
                idxCnt += grp.indexCount;
×
1007
        }
1008
        Q_ASSERT(idxCnt>0);
×
1009
        //only do 1 division for more accuracy
1010
        centroid /= idxCnt;
×
1011
        this->centroid = centroid.toVec3f();
×
1012
}
×
1013

1014
void StelOBJ::generateNormals()
×
1015
{
1016
        //Code adapted from old OBJ loader (Andrei Borza)
1017

1018
        const unsigned int *pTriangle = Q_NULLPTR;
×
1019
        Vertex *pVertex0 = Q_NULLPTR;
×
1020
        Vertex *pVertex1 = Q_NULLPTR;
×
1021
        Vertex *pVertex2 = Q_NULLPTR;
×
1022
        float edge1[3] = {0.0f, 0.0f, 0.0f};
×
1023
        float edge2[3] = {0.0f, 0.0f, 0.0f};
×
1024
        float normal[3] = {0.0f, 0.0f, 0.0f};
×
1025
        float invlength = 0.0f;
×
1026
        int totalVertices = m_vertices.size();
×
1027
        int totalTriangles = m_indices.size() / 3;
×
1028

1029
        // Initialize all the vertex normals.
1030
        for (int i=0; i<totalVertices; ++i)
×
1031
        {
1032
                pVertex0 = &m_vertices[i];
×
1033
                pVertex0->normal[0] = 0.0f;
×
1034
                pVertex0->normal[1] = 0.0f;
×
1035
                pVertex0->normal[2] = 0.0f;
×
1036
        }
1037

1038
        // Calculate the vertex normals.
1039
        for (int i=0; i<totalTriangles; ++i)
×
1040
        {
1041
                pTriangle = &m_indices.at(i*3);
×
1042

1043
                pVertex0 = &m_vertices[static_cast<int>(pTriangle[0])];
×
1044
                pVertex1 = &m_vertices[static_cast<int>(pTriangle[1])];
×
1045
                pVertex2 = &m_vertices[static_cast<int>(pTriangle[2])];
×
1046

1047
                // Calculate triangle face normal.
1048
                edge1[0] = static_cast<float>(pVertex1->position[0] - pVertex0->position[0]);
×
1049
                edge1[1] = static_cast<float>(pVertex1->position[1] - pVertex0->position[1]);
×
1050
                edge1[2] = static_cast<float>(pVertex1->position[2] - pVertex0->position[2]);
×
1051

1052
                edge2[0] = static_cast<float>(pVertex2->position[0] - pVertex0->position[0]);
×
1053
                edge2[1] = static_cast<float>(pVertex2->position[1] - pVertex0->position[1]);
×
1054
                edge2[2] = static_cast<float>(pVertex2->position[2] - pVertex0->position[2]);
×
1055

1056
                normal[0] = (edge1[1]*edge2[2]) - (edge1[2]*edge2[1]);
×
1057
                normal[1] = (edge1[2]*edge2[0]) - (edge1[0]*edge2[2]);
×
1058
                normal[2] = (edge1[0]*edge2[1]) - (edge1[1]*edge2[0]);
×
1059

1060
                // Accumulate the normals.
1061

1062
                pVertex0->normal[0] += normal[0];
×
1063
                pVertex0->normal[1] += normal[1];
×
1064
                pVertex0->normal[2] += normal[2];
×
1065

1066
                pVertex1->normal[0] += normal[0];
×
1067
                pVertex1->normal[1] += normal[1];
×
1068
                pVertex1->normal[2] += normal[2];
×
1069

1070
                pVertex2->normal[0] += normal[0];
×
1071
                pVertex2->normal[1] += normal[1];
×
1072
                pVertex2->normal[2] += normal[2];
×
1073
        }
1074

1075
        // Normalize the vertex normals.
1076
        for (int i=0; i<totalVertices; ++i)
×
1077
        {
1078
                pVertex0 = &m_vertices[i];
×
1079

1080
                invlength = 1.0f / std::sqrt(pVertex0->normal[0]*pVertex0->normal[0] +
×
1081
                                pVertex0->normal[1]*pVertex0->normal[1] +
×
1082
                                pVertex0->normal[2]*pVertex0->normal[2]);
×
1083

1084
                pVertex0->normal[0] *= invlength;
×
1085
                pVertex0->normal[1] *= invlength;
×
1086
                pVertex0->normal[2] *= invlength;
×
1087
        }
1088
}
×
1089

1090
void StelOBJ::generateTangents()
×
1091
{
1092
        //Code adapted from old OBJ loader (Andrei Borza)
1093

1094
        const unsigned int *pTriangle = Q_NULLPTR;
×
1095
        Vertex *pVertex0 = Q_NULLPTR;
×
1096
        Vertex *pVertex1 = Q_NULLPTR;
×
1097
        Vertex *pVertex2 = Q_NULLPTR;
×
1098
        float edge1[3] = {0.0f, 0.0f, 0.0f};
×
1099
        float edge2[3] = {0.0f, 0.0f, 0.0f};
×
1100
        float texEdge1[2] = {0.0f, 0.0f};
×
1101
        float texEdge2[2] = {0.0f, 0.0f};
×
1102
        float tangent[3] = {0.0f, 0.0f, 0.0f};
×
1103
        float bitangent[3] = {0.0f, 0.0f, 0.0f};
×
1104
        float det = 0.0f;
×
1105
        float nDotT = 0.0f;
×
1106
        float bDotB = 0.0f;
×
1107
        float invlength = 0.0f;
×
1108
        const int totalVertices = m_vertices.size();
×
1109
        const int totalTriangles = m_indices.size() / 3;
×
1110

1111
        // Initialize all the vertex tangents and bitangents.
1112
        for (int i=0; i<totalVertices; ++i)
×
1113
        {
1114
                pVertex0 = &m_vertices[i];
×
1115

1116
                pVertex0->tangent[0] = 0.0f;
×
1117
                pVertex0->tangent[1] = 0.0f;
×
1118
                pVertex0->tangent[2] = 0.0f;
×
1119
                pVertex0->tangent[3] = 0.0f;
×
1120

1121
                pVertex0->bitangent[0] = 0.0f;
×
1122
                pVertex0->bitangent[1] = 0.0f;
×
1123
                pVertex0->bitangent[2] = 0.0f;
×
1124
        }
1125

1126
        // Calculate the vertex tangents and bitangents.
1127
        for (int i=0; i<totalTriangles; ++i)
×
1128
        {
1129
                pTriangle = &m_indices.at(i*3);
×
1130

1131
                pVertex0 = &m_vertices[static_cast<int>(pTriangle[0])];
×
1132
                pVertex1 = &m_vertices[static_cast<int>(pTriangle[1])];
×
1133
                pVertex2 = &m_vertices[static_cast<int>(pTriangle[2])];
×
1134

1135
                // Calculate the triangle face tangent and bitangent.
1136

1137
                edge1[0] = static_cast<float>(pVertex1->position[0] - pVertex0->position[0]);
×
1138
                edge1[1] = static_cast<float>(pVertex1->position[1] - pVertex0->position[1]);
×
1139
                edge1[2] = static_cast<float>(pVertex1->position[2] - pVertex0->position[2]);
×
1140

1141
                edge2[0] = static_cast<float>(pVertex2->position[0] - pVertex0->position[0]);
×
1142
                edge2[1] = static_cast<float>(pVertex2->position[1] - pVertex0->position[1]);
×
1143
                edge2[2] = static_cast<float>(pVertex2->position[2] - pVertex0->position[2]);
×
1144

1145
                texEdge1[0] = pVertex1->texCoord[0] - pVertex0->texCoord[0];
×
1146
                texEdge1[1] = pVertex1->texCoord[1] - pVertex0->texCoord[1];
×
1147

1148
                texEdge2[0] = pVertex2->texCoord[0] - pVertex0->texCoord[0];
×
1149
                texEdge2[1] = pVertex2->texCoord[1] - pVertex0->texCoord[1];
×
1150

1151
                det = texEdge1[0]*texEdge2[1] - texEdge2[0]*texEdge1[1];
×
1152

1153
                if (fabs(det) < 1e-6f)
×
1154
                {
1155
                        tangent[0] = 1.0f;
×
1156
                        tangent[1] = 0.0f;
×
1157
                        tangent[2] = 0.0f;
×
1158

1159
                        bitangent[0] = 0.0f;
×
1160
                        bitangent[1] = 1.0f;
×
1161
                        bitangent[2] = 0.0f;
×
1162
                }
1163
                else
1164
                {
1165
                        det = 1.0f / det;
×
1166

1167
                        tangent[0] = (texEdge2[1]*edge1[0] - texEdge1[1]*edge2[0])*det;
×
1168
                        tangent[1] = (texEdge2[1]*edge1[1] - texEdge1[1]*edge2[1])*det;
×
1169
                        tangent[2] = (texEdge2[1]*edge1[2] - texEdge1[1]*edge2[2])*det;
×
1170

1171
                        bitangent[0] = (-texEdge2[0]*edge1[0] + texEdge1[0]*edge2[0])*det;
×
1172
                        bitangent[1] = (-texEdge2[0]*edge1[1] + texEdge1[0]*edge2[1])*det;
×
1173
                        bitangent[2] = (-texEdge2[0]*edge1[2] + texEdge1[0]*edge2[2])*det;
×
1174
                }
1175

1176
                // Accumulate the tangents and bitangents.
1177

1178
                pVertex0->tangent[0] += tangent[0];
×
1179
                pVertex0->tangent[1] += tangent[1];
×
1180
                pVertex0->tangent[2] += tangent[2];
×
1181
                pVertex0->bitangent[0] += bitangent[0];
×
1182
                pVertex0->bitangent[1] += bitangent[1];
×
1183
                pVertex0->bitangent[2] += bitangent[2];
×
1184

1185
                pVertex1->tangent[0] += tangent[0];
×
1186
                pVertex1->tangent[1] += tangent[1];
×
1187
                pVertex1->tangent[2] += tangent[2];
×
1188
                pVertex1->bitangent[0] += bitangent[0];
×
1189
                pVertex1->bitangent[1] += bitangent[1];
×
1190
                pVertex1->bitangent[2] += bitangent[2];
×
1191

1192
                pVertex2->tangent[0] += tangent[0];
×
1193
                pVertex2->tangent[1] += tangent[1];
×
1194
                pVertex2->tangent[2] += tangent[2];
×
1195
                pVertex2->bitangent[0] += bitangent[0];
×
1196
                pVertex2->bitangent[1] += bitangent[1];
×
1197
                pVertex2->bitangent[2] += bitangent[2];
×
1198
        }
1199

1200
        // Orthogonalize and normalize the vertex tangents.
1201
        for (int i=0; i<totalVertices; ++i)
×
1202
        {
1203
                pVertex0 = &m_vertices[i];
×
1204

1205
                // Gram-Schmidt orthogonalize tangent with normal.
1206

1207
                nDotT = pVertex0->normal[0]*pVertex0->tangent[0] +
×
1208
                        pVertex0->normal[1]*pVertex0->tangent[1] +
×
1209
                        pVertex0->normal[2]*pVertex0->tangent[2];
×
1210

1211
                pVertex0->tangent[0] -= pVertex0->normal[0]*nDotT;
×
1212
                pVertex0->tangent[1] -= pVertex0->normal[1]*nDotT;
×
1213
                pVertex0->tangent[2] -= pVertex0->normal[2]*nDotT;
×
1214

1215
                // Normalize the tangent.
1216

1217
                invlength = 1.0f / sqrtf(pVertex0->tangent[0]*pVertex0->tangent[0] +
×
1218
                                      pVertex0->tangent[1]*pVertex0->tangent[1] +
×
1219
                                      pVertex0->tangent[2]*pVertex0->tangent[2]);
×
1220

1221
                pVertex0->tangent[0] *= invlength;
×
1222
                pVertex0->tangent[1] *= invlength;
×
1223
                pVertex0->tangent[2] *= invlength;
×
1224

1225
                // Calculate the handedness of the local tangent space.
1226
                // The bitangent vector is the cross product between the triangle face
1227
                // normal vector and the calculated tangent vector. The resulting
1228
                // bitangent vector should be the same as the bitangent vector
1229
                // calculated from the set of linear equations above. If they point in
1230
                // different directions then we need to invert the cross product
1231
                // calculated bitangent vector. We store this scalar multiplier in the
1232
                // tangent vector's 'w' component so that the correct bitangent vector
1233
                // can be generated in the normal mapping shader's vertex shader.
1234
                //
1235
                // Normal maps have a left handed coordinate system with the origin
1236
                // located at the top left of the normal map texture. The x coordinates
1237
                // run horizontally from left to right. The y coordinates run
1238
                // vertically from top to bottom. The z coordinates run out of the
1239
                // normal map texture towards the viewer. Our handedness calculations
1240
                // must take this fact into account as well so that the normal mapping
1241
                // shader's vertex shader will generate the correct bitangent vectors.
1242
                // Some normal map authoring tools such as Crazybump
1243
                // (http://www.crazybump.com/) includes options to allow you to control
1244
                // the orientation of the normal map normal's y-axis.
1245

1246
                bitangent[0] = (pVertex0->normal[1]*pVertex0->tangent[2]) -
×
1247
                               (pVertex0->normal[2]*pVertex0->tangent[1]);
×
1248
                bitangent[1] = (pVertex0->normal[2]*pVertex0->tangent[0]) -
×
1249
                               (pVertex0->normal[0]*pVertex0->tangent[2]);
×
1250
                bitangent[2] = (pVertex0->normal[0]*pVertex0->tangent[1]) -
×
1251
                               (pVertex0->normal[1]*pVertex0->tangent[0]);
×
1252

1253
                bDotB = bitangent[0]*pVertex0->bitangent[0] +
×
1254
                        bitangent[1]*pVertex0->bitangent[1] +
×
1255
                        bitangent[2]*pVertex0->bitangent[2];
×
1256

1257
                pVertex0->tangent[3] = (bDotB < 0.0f) ? 1.0f : -1.0f;
×
1258

1259
                pVertex0->bitangent[0] = bitangent[0];
×
1260
                pVertex0->bitangent[1] = bitangent[1];
×
1261
                pVertex0->bitangent[2] = bitangent[2];
×
1262
        }
1263
}
×
1264

1265
void StelOBJ::generateAABB()
×
1266
{
1267
        //calculate AABB and centroid for each object
1268
        Vec3d accCentroid(0.);
×
1269
        m_bbox.reset();
×
1270
        for(int i =0;i<m_objects.size();++i)
×
1271
        {
1272
                Vec3d centr(0.);
×
1273
                Object& o = m_objects[i];
×
1274

1275
                o.postprocess(*this,centr);
×
1276
                m_bbox.expand(o.boundingbox);
×
1277
                accCentroid+=centr;
×
1278
        }
1279

1280
        m_centroid = (accCentroid / m_objects.size()).toVec3f();
×
1281
}
×
1282

1283
void StelOBJ::performPostProcessing(bool genNormals)
×
1284
{
1285
        QElapsedTimer timer;
×
1286
        timer.start();
×
1287

1288
        //if no normals have been read at all, generate them (we do not support smoothing groups at the time, so this is quite simple)
1289
        if(genNormals)
×
1290
        {
1291
                generateNormals();
×
1292
                qCDebug(stelOBJ)<<"Normals calculated in"<<timer.restart()<<"ms";
×
1293
        }
1294

1295
        //generate tangent data
1296
        generateTangents();
×
1297
        qCDebug(stelOBJ())<<"Tangents calculated in"<<timer.restart()<<"ms";
×
1298

1299
        generateAABB();
×
1300
        qCDebug(stelOBJ)<<"AABBs/Centroids calculated in"<<timer.elapsed()<<"ms";
×
1301
        qCDebug(stelOBJ)<<"Centroid is at "<<m_centroid;
×
1302
}
×
1303

1304
StelOBJ::ShortIndexList StelOBJ::getShortIndexList() const
×
1305
{
1306
        QElapsedTimer timer;
×
1307
        timer.start();
×
1308

1309
        ShortIndexList ret;
×
1310
        if(!canUseShortIndices())
×
1311
        {
1312
                qCWarning(stelOBJ)<<"Cannot use short indices for OBJ data, it has"<<m_vertices.size()<<"vertices";
×
1313
                return ret;
×
1314
        }
1315

1316
        ret.reserve(m_indices.size());
×
1317
        for(int i =0;i<m_indices.size();++i)
×
1318
        {
1319
                ret.append(static_cast<unsigned short>(m_indices.at(i)));
×
1320
        }
1321

1322
        qCDebug(stelOBJ)<<"Indices converted to short in"<<timer.elapsed()<<"ms";
×
1323
        return ret;
×
1324
}
×
1325

1326
void StelOBJ::scale(double factor)
×
1327
{
1328
        QElapsedTimer timer;
×
1329
        timer.start();
×
1330

1331
        for(int i = 0;i<m_vertices.size();++i)
×
1332
        {
1333
                GLfloat* dat = m_vertices[i].position;
×
1334
                dat[0] *= static_cast<GLfloat>(factor);
×
1335
                dat[1] *= static_cast<GLfloat>(factor);
×
1336
                dat[2] *= static_cast<GLfloat>(factor);
×
1337
        }
1338

1339
        //AABBs must be recalculated
1340
        generateAABB();
×
1341
        qCDebug(stelOBJ)<<"Scaling done in"<<timer.elapsed()<<"ms";
×
1342
}
×
1343

1344
void StelOBJ::transform(const QMatrix4x4 &mat, bool onlyPosition)
×
1345
{
1346
        //matrix for normals/tangents
1347
        QMatrix3x3 normalMat = mat.normalMatrix();
×
1348

1349
        //Transform all vertices and normals by mat
1350
        for(int i=0; i<m_vertices.size(); ++i)
×
1351
        {
1352
                Vertex& pVertex = m_vertices[i];
×
1353

1354
                QVector3D tf = mat.map(QVector3D(pVertex.position[0], pVertex.position[1], pVertex.position[2]));
×
1355
                std::copy(&tf[0],&tf[0]+3,pVertex.position);
×
1356

1357
                if(!onlyPosition)
×
1358
                {
1359
                        tf = normalMat * QVector3D(pVertex.normal[0], pVertex.normal[1], pVertex.normal[2]);
×
1360
                        pVertex.normal[0] = tf.x();
×
1361
                        pVertex.normal[1] = tf.y();
×
1362
                        pVertex.normal[2] = tf.z();
×
1363

1364
                        tf = normalMat * QVector3D(pVertex.tangent[0], pVertex.tangent[1], pVertex.tangent[2]);
×
1365
                        pVertex.tangent[0] = tf.x();
×
1366
                        pVertex.tangent[1] = tf.y();
×
1367
                        pVertex.tangent[2] = tf.z();
×
1368

1369
                        tf = normalMat * QVector3D(pVertex.bitangent[0], pVertex.bitangent[1], pVertex.bitangent[2]);
×
1370
                        pVertex.bitangent[0] = tf.x();
×
1371
                        pVertex.bitangent[1] = tf.y();
×
1372
                        pVertex.bitangent[2] = tf.z();
×
1373
                }
1374
        }
1375

1376
        //Update bounding box in case it changed
1377
        generateAABB();
×
1378
}
×
1379

1380
void StelOBJ::splitVertexData(V3Vec *position,
×
1381
                              V2Vec *texCoord,
1382
                              V3Vec *normal,
1383
                              V3Vec *tangent,
1384
                              V3Vec *bitangent) const
1385
{
1386
        QElapsedTimer timer;
×
1387
        timer.start();
×
1388

1389
        const int size = m_vertices.size();
×
1390
        //resize arrays
1391
        if(position)
×
1392
                position->resize(size);
×
1393
        if(texCoord)
×
1394
                texCoord->resize(size);
×
1395
        if(normal)
×
1396
                normal->resize(size);
×
1397
        if(tangent)
×
1398
                tangent->resize(size);
×
1399
        if(bitangent)
×
1400
                bitangent->resize(size);
×
1401

1402
        for(int i = 0;i<size;++i)
×
1403
        {
1404
                const Vertex& vtx = m_vertices.at(i);
×
1405
                if(position)
×
1406
                        (*position)[i] = Vec3f(vtx.position);
×
1407
                if(texCoord)
×
1408
                        (*texCoord)[i] = Vec2f(vtx.texCoord);
×
1409
                if(normal)
×
1410
                        (*normal)[i] = Vec3f(vtx.normal);
×
1411
                if(tangent)
×
1412
                        (*tangent)[i] = Vec3f(vtx.tangent);
×
1413
                if(bitangent)
×
1414
                        (*bitangent)[i] = Vec3f(vtx.bitangent);
×
1415
        }                
1416

1417
        qCDebug(stelOBJ)<<"Vertex data split in "<<timer.elapsed()<<"ms";
×
1418
}
×
1419

1420
void StelOBJ::clearVertexData()
×
1421
{
1422
        m_vertices.clear();
×
1423
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc