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

Stellarium / stellarium / 15291801018

28 May 2025 04:52AM UTC coverage: 11.931% (-0.02%) from 11.951%
15291801018

push

github

alex-w
Added new set of navigational stars (XIX century)

0 of 6 new or added lines in 2 files covered. (0.0%)

14124 existing lines in 74 files now uncovered.

14635 of 122664 relevant lines covered (11.93%)

18291.42 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
#if USE_FAST_FLOAT
27
# include <fast_float/fast_float.h>
28
namespace sys
29
{
30
using fast_float::from_chars;
31
}
32
using fast_float::from_chars_result;
33
#else
34
# include <charconv>
35
namespace sys
36
{
37
using std::from_chars;
38
}
39
using std::from_chars_result;
40
#endif
41

42
#include <QBuffer>
43
#include <QDir>
44
#include <QElapsedTimer>
45
#include <QFile>
46
#include <QFileInfo>
47
#include <QRegularExpression>
48
#include <QTextStream>
49

50
Q_LOGGING_CATEGORY(stelOBJ,"stel.OBJ")
×
51

52
namespace
53
{
54

55
// Forward int and double versions
56
auto from_chars(const char* first, const char* last, double& value) { return sys::from_chars(first, last, value); }
×
57
auto from_chars(const char* first, const char* last, int& value) { return sys::from_chars(first, last, value); }
×
58
// Override the float version
59
from_chars_result from_chars(const char* first, const char* last, float& value)
×
60
{
61
#if USE_FAST_FLOAT
62
        auto res = fast_float::from_chars(first, last, value);
63
#else
64
        auto res = std::from_chars(first, last, value);
×
65
#endif
66
        if(res.ec == std::errc{}) return res;
×
67
        // If we have an error, this may be underflow (see a paper about this issue:
68
        // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2827r0.html ),
69
        // in which case we want to simply get zero. So, try reading it as a
70
        // double, and if successful, convert it to the output float.
71
        double d;
72
#if USE_FAST_FLOAT
73
        res = fast_float::from_chars(first, last, d);
74
#else
75
        res = std::from_chars(first, last, d);
×
76
#endif
77
        if(res.ec != std::errc{}) return res;
×
78
        value = d;
×
79
        return res;
×
80
}
81

82
// Split line by spaces without allocating memory, including allocation for storing
83
// the result (we assume that the output vector has a reasonable amount of space reserved)
84
void splitBySpacesWithoutEmptyParts(const QByteArray& line, std::vector<std::string_view>& splits)
×
85
{
86
        splits.clear();
×
87
        int startPos = 0;
×
88
        for(int n = 0; n < line.size(); ++n)
×
89
        {
90
                if(line[n] != ' ') continue;
×
91
                const int currStartPos = startPos;
×
92
                startPos = n + 1;
×
93
                if(n == currStartPos) continue; // skip empty entry
×
94
                splits.emplace_back(line.data() + currStartPos, n - currStartPos);
×
95
        }
96
        // store last entry if it's not empty
97
        const int n = line.size();
×
98
        if(n == startPos) return;
×
99
        splits.emplace_back(line.data() + startPos, n - startPos);
×
100
}
101

102
QDebug& operator<<(QDebug& dbg, std::vector<std::string_view> const& strings)
×
103
{
104
        dbg << "{";
×
105
        for(const auto& str : strings)
×
106
                dbg << QString(QByteArray(str.data(), str.size()));
×
107
        dbg << "}";
×
108
        return dbg;
×
109
}
110
}
111

112
StelOBJ::StelOBJ()
×
113
        : m_isLoaded(false)
×
114
{
115
}
×
116

117
StelOBJ::~StelOBJ()
×
118
{
119
}
×
120

121
void StelOBJ::clear()
×
122
{
123
        //just create a new object
124
        *this = StelOBJ();
×
125
}
×
126

127
bool StelOBJ::load(const QString& filename, const VertexOrder vertexOrder, const bool forceCreateNormals)
×
128
{
129
        qCDebug(stelOBJ)<<"Loading"<<filename;
×
130

131
        QElapsedTimer timer;
×
132
        timer.start();
×
133

134
        //construct base path
135
        QFileInfo fi(filename);
×
136

137
        //try to open the file
138
        QFile file(filename);
×
139
        if(!file.open(QIODevice::ReadOnly))
×
140
        {
141
                qCCritical(stelOBJ)<<"Could not open file"<<filename<<file.errorString();
×
142
                return false;
×
143
        }
144

145
        qCDebug(stelOBJ)<<"Opened file in"<<timer.restart()<<"ms";
×
146

147
        //check if this is a compressed file
148
        if(filename.endsWith(".gz"))
×
149
        {
150
                //uncompress into memory
151
                QByteArray data = StelUtils::uncompress(file);
×
152
                file.close();
×
153
                //check if decompressing was successful
154
                if(data.isEmpty())
×
155
                {
156
                        qCCritical(stelOBJ)<<"Could not decompress file"<<filename;
×
157
                        return false;
×
158
                }
159
                qCDebug(stelOBJ)<<"Decompressed in"<<timer.elapsed()<<"ms";
×
160

161
                //create and open a QBuffer for reading
162
                QBuffer buf(&data);
×
163
                buf.open(QIODevice::ReadOnly);
×
164

165
                //perform actual load
166
                return load(buf,fi.canonicalPath(),vertexOrder, forceCreateNormals);
×
167
        }
×
168

169
        //perform actual load
170
        return load(file,fi.canonicalPath(),vertexOrder, forceCreateNormals);
×
171
}
×
172

173
//macro to test out different ways of comparison and their performance
174
#define CMD_CMP(a) (std::string_view(a)==cmd)
175

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

180
bool StelOBJ::parseBool(const ParseParams &params, bool &out, int paramsStart)
×
181
{
182
        if(params.size()-paramsStart<1)
×
183
        {
184
                qCCritical(stelOBJ)<<"Expected parameter for statement"<<params;
×
185
                return false;
×
186
        }
187
        if(params.size()-paramsStart>1)
×
188
        {
189
                qCWarning(stelOBJ)<<"Additional parameters ignored in statement"<<params;
×
190
        }
191

192

193
        const ParseParam& cmd = params.at(paramsStart);
×
194
        out = (CMD_CMP("1") || CMD_CMP("true") || CMD_CMP("TRUE") || CMD_CMP("yes") || CMD_CMP("YES"));
×
195

196
        return true;
×
197
}
198

199
bool StelOBJ::parseInt(const std::string_view& str, int& out)
×
200
{
201
        return from_chars(str.data(), str.data() + str.size(), out).ec == std::errc{};
×
202
}
203

204
bool StelOBJ::parseInt(const ParseParams &params, int &out, int paramsStart)
×
205
{
206
        if(params.size()-paramsStart<1)
×
207
        {
208
                qCCritical(stelOBJ)<<"Expected parameter for statement"<<params;
×
209
                out=0;
×
210
                return false;
×
211
        }
212
        if(params.size()-paramsStart>1)
×
213
        {
214
                qCWarning(stelOBJ)<<"Additional parameters ignored in statement"<<params;
×
215
        }
216

217
        return parseInt(params.at(paramsStart), out);
×
218
}
219

220
bool StelOBJ::parseString(const ParseParams &params, QString &out, int paramsStart)
221
{
222
        if(params.size()-paramsStart<1)
223
        {
224
                qCCritical(stelOBJ)<<"Expected parameter for statement"<<params;
225
                return false;
226
        }
227
        if(params.size()-paramsStart>1)
228
        {
229
                qCWarning(stelOBJ)<<"Additional parameters ignored in statement"<<params;
230
        }
231

232
        out = QString(QByteArray(params[paramsStart].data(), params[paramsStart].size()));
233
        return true;
234
}
235

236
QString StelOBJ::getRestOfString(const QString &strip, const QString &line)
×
237
{
238
        return line.mid(strip.length()).trimmed();
×
239
}
240

241
bool StelOBJ::parseFloat(const ParseParams &params, float &out, int paramsStart)
×
242
{
243
        if(params.size()-paramsStart<1)
×
244
        {
245
                qCCritical(stelOBJ)<<"Expected parameter for statement"<<params;
×
246
                return false;
×
247
        }
248
        if(params.size()-paramsStart>1)
×
249
        {
250
                qCWarning(stelOBJ)<<"Additional parameters ignored in statement"<<params;
×
251
        }
252

253
        const auto& str = params[paramsStart];
×
254
        const auto res = from_chars(str.data(), str.data() + str.size(), out);
×
255
        return res.ec == std::errc{};
×
256
}
257

258
template <typename T>
259
bool StelOBJ::parseVec3(const ParseParams& params, T &out, int paramsStart)
×
260
{
261
        if(params.size()-paramsStart<3)
×
262
        {
263
                qCCritical(stelOBJ)<<"Invalid Vec3f specification"<<params;
×
264
                return false;
×
265
        }
266

267
        const auto& xStr = params[paramsStart+0];
×
268
        const auto& yStr = params[paramsStart+1];
×
269
        const auto& zStr = params[paramsStart+2];
×
270

271
        from_chars_result res;
272
        res = from_chars(xStr.data(), xStr.data() + xStr.size(), out[0]);
×
273
        if (res.ec != std::errc{}) goto error;
×
274

275
        res = from_chars(yStr.data(), yStr.data() + yStr.size(), out[1]);
×
276
        if (res.ec != std::errc{}) goto error;
×
277

278
        res = from_chars(zStr.data(), zStr.data() + zStr.size(), out[2]);
×
279
        if (res.ec != std::errc{}) goto error;
×
280

281
        return true;
×
282

283
error:
×
284
        qCCritical(stelOBJ) << "Error parsing Vec3:" << params;
×
285
        return false;
×
286
}
287

288
template <typename T>
289
bool StelOBJ::parseVec2(const ParseParams& params,T &out, int paramsStart)
×
290
{
291
        if(params.size()-paramsStart<2)
×
292
        {
293
                qCCritical(stelOBJ)<<"Invalid Vec2f specification"<<params;
×
294
                return false;
×
295
        }
296

297
        const auto& xStr = params[paramsStart+0];
×
298
        const auto& yStr = params[paramsStart+1];
×
299

300
        from_chars_result res;
301
        res = from_chars(xStr.data(), xStr.data() + xStr.size(), out[0]);
×
302
        if (res.ec != std::errc{}) goto error;
×
303

304
        res = from_chars(yStr.data(), yStr.data() + yStr.size(), out[1]);
×
305
        if (res.ec != std::errc{}) goto error;
×
306

307
        return true;
×
308

309
error:
×
310
        qCCritical(stelOBJ) << "Error parsing Vec2:" << params;
×
311
        return false;
×
312
}
313

314
StelOBJ::Object* StelOBJ::getCurrentObject(CurrentParserState &state)
×
315
{
316
        //if there is a current object, return this one
317
        if(state.currentObject)
×
318
                return state.currentObject;
×
319

320
        //create the default object
321
        Object& obj = INC_LIST(m_objects);
×
322
        obj.name = "<default object>";
×
323
        obj.isDefaultObject = true;
×
324
        m_objectMap.insert(obj.name, m_objects.size()-1);
×
325
        state.currentObject = &obj;
×
326
        return &obj;
×
327
}
328

329
StelOBJ::MaterialGroup* StelOBJ::getCurrentMaterialGroup(CurrentParserState &state)
×
330
{
331
        int matIdx = getCurrentMaterialIndex(state);
×
332
        //if there is a current material group, check if a new one must be created because the material changed
333
        if(state.currentMaterialGroup && state.currentMaterialGroup->materialIndex==matIdx)
×
334
                return state.currentMaterialGroup;
×
335

336
        //no material group has been created yet
337
        //or the material has changed
338
        //we need to create a new group
339
        //we need an object for this
340
        Object* curObj = getCurrentObject(state);
×
341

342
        MaterialGroup& grp = INC_LIST(curObj->groups);
×
343
        grp.materialIndex = matIdx;
×
344
        //the object should always be the most recently added one
345
        grp.objectIndex = m_objects.size()-1;
×
346
        //the start index is positioned after the end of the index list
347
        grp.startIndex = m_indices.size();
×
348
        state.currentMaterialGroup = &grp;
×
349
        return &grp;
×
350
}
351

352
int StelOBJ::getCurrentMaterialIndex(CurrentParserState &state)
×
353
{
354
        //if there has been a material definition before, we use this
355
        if(m_materials.size())
×
356
                return state.currentMaterialIdx;
×
357

358
        //only if no material has been defined before any face,
359
        //we need to create a default material
360
        //this is "a white material" according to http://paulbourke.net/dataformats/obj/
361
        Material& mat = INC_LIST(m_materials);
×
362
        mat.name = "<default material>";
×
363
        mat.Kd = QVector3D(0.8f, 0.8f, 0.8f);
×
364
        mat.Ka = QVector3D(0.1f, 0.1f, 0.1f);
×
365

366
        m_materialMap.insert(mat.name, m_materials.size()-1);
×
367
        state.currentMaterialIdx = 0;
×
368
        return 0;
×
369
}
370

371
bool StelOBJ::parseFace(const ParseParams& params, const V3Vec& posList, const V3Vec& normList, const V2Vec& texList,
×
372
                        CurrentParserState& state,
373
                        VertexCache& vertCache)
374
{
375
        //The face definition can have 4 different variants
376
        //Mode 1: Only position:                f v1 v2 v3
377
        //Mode 2: Position+texcoords:                f v1/t1 v2/t2 v3/t3
378
        //Mode 3: Position+texcoords+normals:        f v1/t1/n1 v2/t2/n2 v3/t3/n3
379
        //Mode 4: Position+normals:                f v1//n1 v2//n2 v3//n3
380

381
        // Zero is actually invalid in the face definition, so we use it for default values
382
        int posIdx=0, texIdx=0, normIdx=0;
×
383
        // Contains the vertex indices
384
        QVarLengthArray<unsigned int,16> vIdx;
×
385

386
        if(params.size()<4)
×
387
        {
388
                qCCritical(stelOBJ)<<"Invalid number of vertices in face statement"<<params;
×
389
                return false;
×
390
        }
391

392
        int vtxAmount = params.size()-1;
×
393

394
        //parse each one separately
395
        int mode = 0;
×
396
        bool ok = false;
×
397
        //a macro for consistency check
398
        #define CHK_MODE(a) if(mode && mode!=a) { qCCritical(stelOBJ)<<"Inconsistent face statement"<<params; return false; } else {mode = a;}
399
        //a macro for checking number parsing
400
        #define CHK_OK(a) do{ a; if(!ok) { qCCritical(stelOBJ)<<"Could not parse number in face statement"<<params; return false; } } while(0)
401
        //negative indices indicate relative data, i.e. -1 would mean the last position/texture/normal that was parsed
402
        //this macro fixes it up so that it always uses absolute numbers
403
        //note: the indices start with 1, this is fixed up later
404
        #define FIX_REL(a, list) if(a<0) {a += list.size()+1; }
405

406
        // Find all slashes without allocating memory, including allocation for storing
407
        // the result (we assume that the output vector has a reasonable amount of space reserved)
408
        const auto findSlashes = [](const std::string_view& line, std::vector<int>& slashes)
×
409
        {
410
                slashes.clear();
×
411
                for(unsigned n = 0; n < line.size(); ++n)
×
412
                        if(line[n] == '/')
×
413
                                slashes.push_back(n);
×
414
        };
×
415

416
        thread_local std::vector<int> slashes;
×
417
        slashes.reserve(4);
×
418
        //loop to parse each section separately
419
        for(int i =0; i<vtxAmount;++i)
×
420
        {
421
                const auto& vertStr = params[i+1];
×
422
                findSlashes(vertStr, slashes);
×
423

424
                switch(slashes.size())
×
425
                {
426
                        case 0:
×
427
                                //no slash, only position
428
                                CHK_MODE(1)
×
429
                                CHK_OK(ok = parseInt(vertStr, posIdx));
×
430
                                FIX_REL(posIdx, posList)
×
431
                                break;
×
432
                        case 1: //single slash, vert/tex
×
433
                                CHK_MODE(2)
×
434
                                CHK_OK(ok = parseInt(vertStr.substr(0, slashes[0]), posIdx));
×
435
                                FIX_REL(posIdx, posList)
×
436
                                CHK_OK(ok = parseInt(vertStr.substr(slashes[0] + 1, vertStr.size() - (slashes[0] + 1)), texIdx));
×
437
                                FIX_REL(texIdx, texList)
×
438
                                break;
×
439
                        case 2: //2 slashes, either v/t/n or v//n
×
440
                                if(slashes[1]-slashes[0] != 1)
×
441
                                {
442
                                        CHK_MODE(3)
×
443
                                        CHK_OK(ok = parseInt(vertStr.substr(0, slashes[0]), posIdx));
×
444
                                        FIX_REL(posIdx, posList)
×
445
                                        CHK_OK(ok = parseInt(vertStr.substr(slashes[0] + 1, slashes[1] - (slashes[0] + 1)), texIdx));
×
446
                                        FIX_REL(texIdx, texList)
×
447
                                        CHK_OK(ok = parseInt(vertStr.substr(slashes[1] + 1, vertStr.size() - (slashes[1] + 1)), normIdx));
×
448
                                        FIX_REL(normIdx, normList)
×
449
                                }
450
                                else
451
                                {
452
                                        CHK_MODE(4)
×
453
                                        CHK_OK(ok = parseInt(vertStr.substr(0, slashes[0]), posIdx));
×
454
                                        FIX_REL(posIdx, posList)
×
455
                                        CHK_OK(ok = parseInt(vertStr.substr(slashes[1] + 1, vertStr.size() - (slashes[1] + 1)), normIdx));
×
456
                                        FIX_REL(normIdx, normList)
×
457
                                }
458
                                break;
×
459
                        default: //invalid line
×
460
                                qCCritical(stelOBJ)<<"Invalid face statement"<<params;
×
461
                                return false;
×
462
                }
463

464
                //create a temporary Vertex by copying the info from the lists
465
                //zero initialize!
466
                Vertex v = Vertex();
×
467
                if(posIdx)
×
468
                {
469
                        const float* data = posList.at(posIdx-1).v;
×
470
                        std::copy(data, data+3, v.position);
×
471
                }
472
                if(texIdx)
×
473
                {
474
                        const float* data = texList.at(texIdx-1).v;
×
475
                        std::copy(data, data+2, v.texCoord);
×
476
                }
477
                if(normIdx)
×
478
                {
479
                        const float* data = normList.at(normIdx-1).v;
×
480
                        std::copy(data, data+3, v.normal);
×
481
                }
482

483
                //check if the vertex is already in the vertex cache
484
                auto it = vertCache.find(v);
×
485
                if(it!=vertCache.end())
×
486
                {
487
                        //cache hit, reuse index
488
                        vIdx.append(*it);
×
489
                }
490
                else
491
                {
492
                        //vertex unknown, add it to the vertex list and cache
493
                        unsigned int idx = static_cast<unsigned int>(m_vertices.size());
×
494
                        vertCache.insert(v,m_vertices.size());
×
495
                        m_vertices.append(v);
×
496
                        vIdx.append(idx);
×
497
                }
498
        }
499

500
        //get/create current material group
501
        MaterialGroup* grp = getCurrentMaterialGroup(state);
×
502

503
        //vertex data has been loaded, create the faces
504
        //we use triangle-fan triangulation
505
        for(int i=2;i<vtxAmount;++i)
×
506
        {
507
                //the first one is always the same
508
                m_indices.append(vIdx[0]);
×
509
                m_indices.append(vIdx[i-1]);
×
510
                m_indices.append(vIdx[i]);
×
511
                //add the triangle to the group
512
                grp->indexCount+=3;
×
513
        }
514

515
        return true;
×
516
}
×
517

518
StelOBJ::MaterialList StelOBJ::Material::loadFromFile(const QString &filename)
×
519
{
520
        StelOBJ::MaterialList list;
×
521

522
        QFileInfo fi(filename);
×
523
        QDir dir = fi.dir();
×
524
        QFile file(filename);
×
525
        if(!file.open(QIODevice::ReadOnly))
×
526
        {
527
                qCWarning(stelOBJ)<<"Could not open MTL file"<<filename<<file.errorString();
×
528
                return list;
×
529
        }
530

531
        Material* curMaterial = Q_NULLPTR;
×
532
        int lineNr = 0;
×
533
        // some exporters give d and Tr, and some give contradicting interpretations. Track a warning with these.
534
        bool dHasBeenGiven = false;
×
535
        bool trHasBeenGiven = false;
×
536

537
        ParseParams splits;
×
538
        while(!file.atEnd())
×
539
        {
540
                ++lineNr;
×
541
                bool ok = true;
×
542
                //make sure only spaces are the separator
543
                const QByteArray line = file.readLine().simplified();
×
544

545
                splitBySpacesWithoutEmptyParts(line, splits);
×
546

547
                if(!splits.empty())
×
548
                {
549
                        const ParseParam& cmd = splits[0];
×
550

551
                        //macro to make sure a material is currently active
552
                        #define CHECK_MTL() if(!curMaterial) { ok = false; qCCritical(stelOBJ)<<"Encountered material statement without active material"; }
553
                        //macro to make path absolute, also to force use of forward slashes
554
                        #define MAKE_ABS(a) if(!a.isEmpty()){ a = dir.absoluteFilePath(QDir::cleanPath(a.replace('\\','/'))); }
555
                        if(CMD_CMP("newmtl")) //define new material
×
556
                        {
557
                                //use rest of line to support spaces in file name
558
                                QString name = getRestOfString(QStringLiteral("newmtl"),line);
×
559
                                ok = !name.isEmpty();
×
560
                                if(ok)
×
561
                                {
562
                                        //add a new material with the specified name
563
                                        curMaterial = &INC_LIST(list);
×
564
                                        curMaterial->name = name;
×
565
                                        dHasBeenGiven = false;
×
566
                                        trHasBeenGiven = false;
×
567
                                }
568
                                else
569
                                {
570
                                        qCCritical(stelOBJ)<<"Invalid newmtl statement"<<line;
×
571
                                }
572
                        }
×
573
                        else if(CMD_CMP("Ka")) //define ambient color
×
574
                        {
575
                                CHECK_MTL()
×
576
                                if(ok)
×
577
                                        ok = parseVec3(splits,curMaterial->Ka);
×
578
                        }
579
                        else if(CMD_CMP("Kd")) //define diffuse color
×
580
                        {
581
                                CHECK_MTL()
×
582
                                if(ok)
×
583
                                        ok = parseVec3(splits,curMaterial->Kd);
×
584
                        }
585
                        else if(CMD_CMP("Ks")) //define specular color
×
586
                        {
587
                                CHECK_MTL()
×
588
                                if(ok)
×
589
                                        ok = parseVec3(splits,curMaterial->Ks);
×
590
                        }
591
                        else if(CMD_CMP("Ke")) //define emissive color
×
592
                        {
593
                                CHECK_MTL()
×
594
                                if(ok)
×
595
                                        ok = parseVec3(splits,curMaterial->Ke);
×
596
                        }
597
                        else if(CMD_CMP("Ns")) //define specular coefficient
×
598
                        {
599
                                CHECK_MTL()
×
600
                                if(ok)
×
601
                                        ok = StelOBJ::parseFloat(splits,curMaterial->Ns);
×
602
                        }
603
                        else if(CMD_CMP("d"))
×
604
                        {
605
                                CHECK_MTL()
×
606
                                if(ok)
×
607
                                {
608
                                        ok = StelOBJ::parseFloat(splits,curMaterial->d);
×
609
                                        //clamp d to [0,1]
610
                                        curMaterial->d = qBound(0.0f, curMaterial->d, 1.0f);
×
611
                                        if (trHasBeenGiven)
×
612
                                        {
613
                                                qWarning(stelOBJ) << "Material" << curMaterial->name << "warning: Tr and d both given. The latter wins, d=" << curMaterial->d;
×
614
                                        }
615
                                        dHasBeenGiven=true;
×
616
                                }
617
                        }
618
                        else if(CMD_CMP("Tr"))
×
619
                        {
620
                                CHECK_MTL()
×
621
                                if(ok)
×
622
                                {
623
                                        //Tr should be the inverse of d, in theory
624
                                        //not all exporters seem to follow this rule...
625
                                        ok = StelOBJ::parseFloat(splits,curMaterial->d);
×
626
                                        //clamp d to [0,1]
627
                                        curMaterial->d = 1.0f - qBound(0.0f, curMaterial->d, 1.0f);
×
628
                                        if (dHasBeenGiven)
×
629
                                        {
630
                                                qWarning(stelOBJ) << "Material" << curMaterial->name << "warning: d and Tr both given. The latter wins, Tr=" << 1.0f-curMaterial->d;
×
631
                                        }
632
                                        trHasBeenGiven=true;
×
633
                                }
634
                        }
635
                        else if(CMD_CMP("map_Ka")) //define ambient map
×
636
                        {
637
                                CHECK_MTL()
×
638
                                if(ok)
×
639
                                {
640
                                        //use rest of line to support spaces in file name
641
                                        curMaterial->map_Ka = getRestOfString(QStringLiteral("map_Ka"),line);
×
642
                                        ok = !curMaterial->map_Ka.isEmpty();
×
643
                                        MAKE_ABS(curMaterial->map_Ka)
×
644
                                }
645
                        }
646
                        else if(CMD_CMP("map_Kd")) //define diffuse map
×
647
                        {
648
                                CHECK_MTL()
×
649
                                if(ok)
×
650
                                {
651
                                        //use rest of line to support spaces in file name
652
                                        curMaterial->map_Kd = getRestOfString(QStringLiteral("map_Kd"),line);
×
653
                                        ok = !curMaterial->map_Kd.isEmpty();
×
654
                                        MAKE_ABS(curMaterial->map_Kd)
×
655
                                }
656
                        }
657
                        else if(CMD_CMP("map_Ks")) //define specular map
×
658
                        {
659
                                CHECK_MTL()
×
660
                                if(ok)
×
661
                                {
662
                                        //use rest of line to support spaces in file name
663
                                        curMaterial->map_Ks = getRestOfString(QStringLiteral("map_Ks"),line);
×
664
                                        ok = !curMaterial->map_Ks.isEmpty();
×
665
                                        MAKE_ABS(curMaterial->map_Ks)
×
666
                                }
667
                        }
668
                        else if(CMD_CMP("map_Ke")) //define emissive map
×
669
                        {
670
                                CHECK_MTL()
×
671
                                if(ok)
×
672
                                {
673
                                        //use rest of line to support spaces in file name
674
                                        curMaterial->map_Ke = getRestOfString(QStringLiteral("map_Ke"),line);
×
675
                                        ok = !curMaterial->map_Ke.isEmpty();
×
676
                                        MAKE_ABS(curMaterial->map_Ke)
×
677
                                }
678
                        }
679
                        else if(CMD_CMP("map_bump")) //define bump/normal map
×
680
                        {
681
                                CHECK_MTL()
×
682
                                if(ok)
×
683
                                {
684
                                        //use rest of line to support spaces in file name
685
                                        curMaterial->map_bump = getRestOfString(QStringLiteral("map_bump"),line);
×
686
                                        ok = !curMaterial->map_bump.isEmpty();
×
687
                                        MAKE_ABS(curMaterial->map_bump)
×
688
                                }
689
                        }
690
                        else if(CMD_CMP("map_height")) //define height map
×
691
                        {
692
                                CHECK_MTL()
×
693
                                if(ok)
×
694
                                {
695
                                        //use rest of line to support spaces in file name
696
                                        curMaterial->map_height = getRestOfString(QStringLiteral("map_height"),line);
×
697
                                        ok = !curMaterial->map_height.isEmpty();
×
698
                                        MAKE_ABS(curMaterial->map_height)
×
699
                                }
700
                        }
701
                        else if(CMD_CMP("illum"))
×
702
                        {
703
                                CHECK_MTL()
×
704
                                if(ok)
×
705
                                {
706
                                        int tmp;
707
                                        ok = parseInt(splits,tmp);
×
708
                                        curMaterial->illum = static_cast<Illum>(tmp);
×
709

710
                                        if(tmp<I_DIFFUSE || tmp > I_TRANSLUCENT)
×
711
                                        {
712
                                                ok = false;
×
713
                                                tmp = I_NONE;
×
714
                                                qCCritical(stelOBJ())<<"Invalid illum statement"<<line;
×
715
                                        }
716

717
                                        //if between these 2, set to translucent and warn
718
                                        if(tmp>I_SPECULAR && tmp < I_TRANSLUCENT)
×
719
                                        {
720
                                                qCWarning(stelOBJ())<<"Treating illum "<<tmp<<"as TRANSLUCENT";
×
721
                                                tmp = I_TRANSLUCENT;
×
722
                                        }
723
                                        curMaterial->illum = static_cast<Illum>(tmp);
×
724
                                }
725
                        }
726
                        else if(!cmd.empty() && cmd[0] !='#')
×
727
                        {
728
                                CHECK_MTL()
×
729
                                if(ok)
×
730
                                {
731
                                        //unknown command, add to additional params
732
                                        //we need to convert to actual string instances to store them
733
                                        QStringList list;
×
734
                                        for(unsigned i = 1; i<splits.size();++i)
×
735
                                        {
736
                                                list.append(QString(QByteArray(splits[i].data(), splits[i].size())));
×
737
                                        }
738

739
                                        curMaterial->additionalParams.insert(QString(QByteArray(cmd.data(), cmd.size())), list);
×
740
                                        //qCWarning(stelOBJ)<<"Unknown MTL statement:"<<line;
741
                                }
×
742
                        }
743
                }
744

745
                if(!ok)
×
746
                {
747
                        list.clear();
×
748
                        qCCritical(stelOBJ)<<"Critical error in MTL file"<<filename<<"at line"<<lineNr<<", cannot process: "<<line;
×
749
                        break;
×
750
                }
751
        }
×
752

753
        return list;
×
754
}
×
755

756
bool StelOBJ::Material::parseBool(const QStringList &params, bool &out)
×
757
{
758
        ParseParams pp(params.size());
×
759
        for(int i = 0; i< params.size();++i)
×
760
        {
761
                pp[i] = params[i].toStdString();
×
762
        }
763
        return StelOBJ::parseBool(pp,out,0);
×
764
}
×
765

766
bool StelOBJ::Material::parseFloat(const QStringList &params, float &out)
×
767
{
768
        ParseParams pp(params.size());
×
769
        for(int i = 0; i< params.size();++i)
×
770
        {
771
                pp[i] = params[i].toStdString();
×
772
        }
773
        return StelOBJ::parseFloat(pp,out,0);
×
774
}
×
775

776
bool StelOBJ::Material::parseVec2d(const QStringList &params, Vec2d &out)
×
777
{
778
        ParseParams pp(params.size());
×
779
        for(int i = 0; i< params.size();++i)
×
780
        {
781
                pp[i] = params[i].toStdString();
×
782
        }
783
        return StelOBJ::parseVec2(pp,out,0);
×
784
}
×
785

786
void StelOBJ::addObject(const QString &name, CurrentParserState &state)
×
787
{
788
        //check if the last object contained anything, if not remove it
789
        if(state.currentObject)
×
790
        {
791
                if(state.currentObject->groups.isEmpty())
×
792
                {
793
                        Q_ASSERT(state.currentObject == &m_objects.last());
×
794
                        m_objectMap.remove(state.currentObject->name);
×
795
                        m_objects.removeLast();
×
796
                }
797
        }
798

799
        //create new object
800
        Object& obj = INC_LIST(m_objects);
×
801
        obj.name = name;
×
802
        m_objectMap.insert(obj.name,m_objects.size()-1);
×
803
        state.currentObject = &obj;
×
804
        //also clear material group to make sure a new group is created
805
        state.currentMaterialGroup = Q_NULLPTR;
×
806
}
×
807

808
bool StelOBJ::load(QIODevice& device, const QString &basePath, const VertexOrder vertexOrder, const bool forceCreateNormals)
×
809
{
810
        clear();
×
811

812
        QDir baseDir(basePath);
×
813

814
        QElapsedTimer timer;
×
815
        timer.start();
×
816

817
        bool smoothGroupWarned = false;
×
818
        bool vertexWWarned = false;
×
819
        bool textureWWarned = false;
×
820

821
        //contains the parsed vertex positions
822
        V3Vec posList;
×
823
        //contains the parsed normals
824
        V3Vec normalList;
×
825
        //contains the parsed texture coords
826
        V2Vec texList;
×
827

828
        VertexCache vertCache;
×
829
        CurrentParserState state = CurrentParserState();
×
830

831
        int lineNr=0;
×
832

833
        ParseParams splits;
×
834
        //read file line by line
835
        while(!device.atEnd())
×
836
        {
837
                ++lineNr;
×
838
                //ignore front/back whitespace
839
                const QByteArray line = device.readLine().trimmed();
×
840
                splitBySpacesWithoutEmptyParts(line, splits);
×
841

842
                if(!splits.empty())
×
843
                {
844
                        const ParseParam& cmd = splits[0];
×
845

846
                        bool ok = true;
×
847

848
                        if(CMD_CMP("f"))
×
849
                        {
850
                                ok = parseFace(splits,posList,normalList,texList,state,vertCache);
×
851
                        }
852
                        else if(CMD_CMP("v"))
×
853
                        {
854
                                //we have to handle the vertex order
855
                                Vec3f& target = INC_LIST(posList);
×
856
                                ok = parseVec3(splits,target);
×
857
                                //check the optional w coord if we have a vec4, must be 1
858
                                if(splits.size()>4)
×
859
                                {
860
                                        float w;
861
                                        parseFloat(splits,w,4);
×
862
                                        if((!qFuzzyCompare(w,1.0f)) && (!vertexWWarned))
×
863
                                        {
864
                                                qWarning(stelOBJ)<<"Vertex w coordinates different from 1.0 are not supported, changed to 1.0, starting on line"<<lineNr;
×
865
                                                vertexWWarned=true;
×
866
                                        }
867
                                }
868
                                switch(vertexOrder)
×
869
                                {
870
                                        case XYZ:
×
871
                                                //no change
872
                                                break;
×
873
                                        case XZY:
×
874
                                                target.set(target[0],-target[2],target[1]);
×
875
                                                break;
×
876
                                        case YXZ:
×
877
                                                target.set(target[1],target[0],target[2]);
×
878
                                                break;
×
879
                                        case YZX:
×
880
                                                target.set(target[1],target[2],target[0]);
×
881
                                                break;
×
882
                                        case ZXY:
×
883
                                                target.set(target[2],target[0],target[1]);
×
884
                                                break;
×
885
                                        case ZYX:
×
886
                                                target.set(target[2],target[1],target[0]);
×
887
                                                break;
×
888
                                        default:
×
889
                                                Q_ASSERT_X(0,"StelOBJ::load","invalid vertex order found");
×
890
                                                qCWarning(stelOBJ) << "Vertex order"<<vertexOrder<<"not implemented, assuming XYZ";
891
                                                break;
892
                                }
893
                        }
894
                        else if(CMD_CMP("vt"))
×
895
                        {
896
                                ok = parseVec2(splits,INC_LIST(texList));
×
897
                                //check the optional w coord if we have a vec3, must be 0
898
                                if(splits.size()>3)
×
899
                                {
900
                                        float w;
901
                                        parseFloat(splits,w,3);
×
902
                                        if( (!qFuzzyIsNull(w)) && (!textureWWarned))
×
903
                                        {
904
                                                qWarning(stelOBJ)<<"Texture w coordinates are not supported, starting on line"<<lineNr;
×
905
                                                textureWWarned=true;
×
906
                                        }
907
                                }
908
                        }
909
                        else if(CMD_CMP("vn"))
×
910
                        {
911
                                //we have to handle the vertex order
912
                                Vec3f& target = INC_LIST(normalList);
×
913
                                ok = parseVec3(splits,target);
×
914
                                switch(vertexOrder)
×
915
                                {
916
                                        case XYZ:
×
917
                                                //no change
918
                                                break;
×
919
                                        case XZY:
×
920
                                                target.set(target[0],-target[2],target[1]);
×
921
                                                break;
×
922
                                        case YXZ:
×
923
                                                target.set(target[1],target[0],target[2]);
×
924
                                                break;
×
925
                                        case YZX:
×
926
                                                target.set(target[1],target[2],target[0]);
×
927
                                                break;
×
928
                                        case ZXY:
×
929
                                                target.set(target[2],target[0],target[1]);
×
930
                                                break;
×
931
                                        case ZYX:
×
932
                                                target.set(target[2],target[1],target[0]);
×
933
                                                break;
×
934
                                        default:
×
935
                                                Q_ASSERT_X(0,"StelOBJ::load","invalid vertex order found");
×
936
                                                qCWarning(stelOBJ) << "Vertex order"<<vertexOrder<<"not implemented, assuming XYZ";
937
                                                break;
938
                                }
939
                                //normalize is usually not needed so we skip it
940
                                //target.normalize();
941
                        }
942
                        else if(CMD_CMP("usemtl"))
×
943
                        {
944
                                //use the rest of the string
945
                                QString mtl = getRestOfString(QStringLiteral("usemtl"),line);
×
946
                                ok = !mtl.isEmpty();
×
947
                                if(ok)
×
948
                                {
949
                                        if(m_materialMap.contains(mtl))
×
950
                                        {
951
                                                //set material as active
952
                                                state.currentMaterialIdx = m_materialMap.value(mtl);
×
953
                                        }
954
                                        else
955
                                        {
956
                                                ok = false;
×
957
                                                qCCritical(stelOBJ)<<"Unknown material"<<mtl<<"has been referenced";
×
958
                                        }
959
                                }
960
                                else
961
                                        qCCritical(stelOBJ)<<"No material name given";
×
962
                        }
×
963
                        else if(CMD_CMP("mtllib"))
×
964
                        {
965
                                //use the rest of the string
966
                                QString fileName = getRestOfString(QStringLiteral("mtllib"),line);
×
967
                                ok = !fileName.isEmpty();
×
968
                                if(ok)
×
969
                                {
970
                                        //load external material file
971
                                        const MaterialList newMaterials = Material::loadFromFile(baseDir.absoluteFilePath(fileName));
×
972
                                        for (const auto& m : newMaterials)
×
973
                                        {
974
                                                m_materials.append(m);
×
975
                                                //the map has the index of the material
976
                                                //because pointers may change during parsing
977
                                                //because of list resizeing
978
                                                m_materialMap.insert(m.name,m_materials.size()-1);
×
979
                                        }
980
                                        qCDebug(stelOBJ)<<newMaterials.size()<<"materials loaded from MTL file"<<fileName;
×
981
                                }
×
982
                                else
983
                                        qCCritical(stelOBJ)<<"No material file name given";
×
984
                        }
×
985
                        else if(CMD_CMP("o"))
×
986
                        {
987
                                //use the rest of the string
988
                                QString objName = getRestOfString(QStringLiteral("o"),line);
×
989
                                ok = !objName.isEmpty();
×
990
                                if(ok)
×
991
                                {
992
                                        addObject(objName, state);
×
993
                                }
994
                                else
995
                                        qCCritical(stelOBJ)<<"Object name is required";
×
996
                        }
×
997
                        else if(CMD_CMP("g"))
×
998
                        {
999
                                //use the rest of the string
1000
                                QString objName = getRestOfString(QStringLiteral("g"),line);
×
1001
                                ok = !objName.isEmpty();
×
1002
                                if(ok)
×
1003
                                {
1004
                                        addObject(objName, state);
×
1005
                                }
1006
                                else
1007
                                        qCCritical(stelOBJ)<<"Group name is required";
×
1008
                        }
×
1009
                        else if(CMD_CMP("s"))
×
1010
                        {
1011
                                if(!smoothGroupWarned)
×
1012
                                {
1013
                                        qCWarning(stelOBJ)<<"Smoothing groups are not supported, consider re-exporting your model from Blender";
×
1014
                                        smoothGroupWarned = true;
×
1015
                                }
1016
                        }
1017
                        else if(!cmd.empty() && cmd[0] !='#')
×
1018
                        {
1019
                                //unknown command, warn
1020
                                qCWarning(stelOBJ)<<"Unknown OBJ statement:"<<line;
×
1021
                        }
1022

1023
                        if(!ok)
×
1024
                        {
1025
                                qCCritical(stelOBJ)<<"Critical error on OBJ line"<<lineNr<<", cannot load OBJ data: "<<line;
×
1026
                                return false;
×
1027
                        }
1028
                }
1029
        }
×
1030

1031
        device.close();
×
1032

1033
        //finished loading, squeeze the arrays to save some memory
1034
        m_vertices.squeeze();
×
1035
        m_indices.squeeze();
×
1036

1037
        Q_ASSERT(m_indices.size() % 3 == 0);
×
1038

1039
        qCDebug(stelOBJ)<<"Loaded OBJ in"<<timer.elapsed()<<"ms";
×
1040
        qCDebug(stelOBJ, "Parsed %d positions, %d normals, %d texture coordinates, %d materials",
×
1041
                int(posList.size()), int(normalList.size()), int(texList.size()), int(m_materials.size()));
1042
        qCDebug(stelOBJ, "Created %d vertices, %d faces, %d objects", int(m_vertices.size()), getFaceCount(), int(m_objects.size()));
×
1043

1044
        //perform post processing
1045
        performPostProcessing(normalList.isEmpty() || forceCreateNormals);
×
1046
        m_isLoaded = true;
×
1047
        return true;
×
1048
}
×
1049

1050
void StelOBJ::Object::postprocess(const StelOBJ &obj, Vec3d &centroid)
×
1051
{
1052
        const VertexList& vList = obj.getVertexList();
×
1053
        const IndexList& iList = obj.getIndexList();
×
1054

1055
        int idxCnt = 0;
×
1056
        boundingbox.reset();
×
1057

1058
        //iterate through the groups
1059
        for(int i =0;i<groups.size();++i)
×
1060
        {
1061
                MaterialGroup& grp = groups[i];
×
1062
                Vec3d accVertex(0.);
×
1063

1064
                Q_ASSERT(grp.indexCount > 0);
×
1065
                grp.boundingbox.reset();
×
1066

1067
                //iterate through the vertices of the group
1068
                for(int idx = grp.startIndex;idx<(grp.startIndex+grp.indexCount);++idx)
×
1069
                {
1070
                        const Vertex& v = vList.at(static_cast<int>(iList.at(idx)));
×
1071
                        Vec3f pos(v.position);
×
1072
                        grp.boundingbox.expand(pos);
×
1073
                        accVertex+=pos.toVec3d();
×
1074
                        centroid+=pos.toVec3d();
×
1075
                }
1076
                boundingbox.expand(grp.boundingbox);
×
1077
                grp.centroid = (accVertex / grp.indexCount).toVec3f();
×
1078

1079
                idxCnt += grp.indexCount;
×
1080
        }
1081
        Q_ASSERT(idxCnt>0);
×
1082
        //only do 1 division for more accuracy
1083
        centroid /= idxCnt;
×
1084
        this->centroid = centroid.toVec3f();
×
1085
}
×
1086

1087
void StelOBJ::generateNormals()
×
1088
{
1089
        //Code adapted from old OBJ loader (Andrei Borza)
1090

1091
        const unsigned int *pTriangle = Q_NULLPTR;
×
1092
        Vertex *pVertex0 = Q_NULLPTR;
×
1093
        Vertex *pVertex1 = Q_NULLPTR;
×
1094
        Vertex *pVertex2 = Q_NULLPTR;
×
1095
        float edge1[3] = {0.0f, 0.0f, 0.0f};
×
1096
        float edge2[3] = {0.0f, 0.0f, 0.0f};
×
1097
        float normal[3] = {0.0f, 0.0f, 0.0f};
×
1098
        int totalVertices = m_vertices.size();
×
1099
        int totalTriangles = m_indices.size() / 3;
×
1100

1101
        // Initialize all the vertex normals.
UNCOV
1102
        for (int i=0; i<totalVertices; ++i)
×
1103
        {
UNCOV
1104
                pVertex0 = &m_vertices[i];
×
1105
                pVertex0->normal[0] = 0.0f;
×
1106
                pVertex0->normal[1] = 0.0f;
×
1107
                pVertex0->normal[2] = 0.0f;
×
1108
        }
1109

1110
        // Calculate the vertex normals.
UNCOV
1111
        for (int i=0; i<totalTriangles; ++i)
×
1112
        {
UNCOV
1113
                pTriangle = &m_indices.at(i*3);
×
1114

UNCOV
1115
                pVertex0 = &m_vertices[static_cast<int>(pTriangle[0])];
×
1116
                pVertex1 = &m_vertices[static_cast<int>(pTriangle[1])];
×
1117
                pVertex2 = &m_vertices[static_cast<int>(pTriangle[2])];
×
1118

1119
                // Calculate triangle face normal.
UNCOV
1120
                edge1[0] = static_cast<float>(pVertex1->position[0] - pVertex0->position[0]);
×
1121
                edge1[1] = static_cast<float>(pVertex1->position[1] - pVertex0->position[1]);
×
1122
                edge1[2] = static_cast<float>(pVertex1->position[2] - pVertex0->position[2]);
×
1123

UNCOV
1124
                edge2[0] = static_cast<float>(pVertex2->position[0] - pVertex0->position[0]);
×
1125
                edge2[1] = static_cast<float>(pVertex2->position[1] - pVertex0->position[1]);
×
1126
                edge2[2] = static_cast<float>(pVertex2->position[2] - pVertex0->position[2]);
×
1127

UNCOV
1128
                normal[0] = (edge1[1]*edge2[2]) - (edge1[2]*edge2[1]);
×
1129
                normal[1] = (edge1[2]*edge2[0]) - (edge1[0]*edge2[2]);
×
1130
                normal[2] = (edge1[0]*edge2[1]) - (edge1[1]*edge2[0]);
×
1131

1132
                // Accumulate the normals.
1133

UNCOV
1134
                pVertex0->normal[0] += normal[0];
×
1135
                pVertex0->normal[1] += normal[1];
×
1136
                pVertex0->normal[2] += normal[2];
×
1137

UNCOV
1138
                pVertex1->normal[0] += normal[0];
×
1139
                pVertex1->normal[1] += normal[1];
×
1140
                pVertex1->normal[2] += normal[2];
×
1141

UNCOV
1142
                pVertex2->normal[0] += normal[0];
×
1143
                pVertex2->normal[1] += normal[1];
×
1144
                pVertex2->normal[2] += normal[2];
×
1145
        }
1146

1147
        // Normalize the vertex normals.
UNCOV
1148
        for (int i=0; i<totalVertices; ++i)
×
1149
        {
UNCOV
1150
                pVertex0 = &m_vertices[i];
×
1151

UNCOV
1152
                float invlength = 1.0f / std::sqrt(pVertex0->normal[0]*pVertex0->normal[0] +
×
1153
                                pVertex0->normal[1]*pVertex0->normal[1] +
×
1154
                                pVertex0->normal[2]*pVertex0->normal[2]);
×
1155

UNCOV
1156
                pVertex0->normal[0] *= invlength;
×
1157
                pVertex0->normal[1] *= invlength;
×
1158
                pVertex0->normal[2] *= invlength;
×
1159
        }
UNCOV
1160
}
×
1161

UNCOV
1162
void StelOBJ::generateTangents()
×
1163
{
1164
        //Code adapted from old OBJ loader (Andrei Borza)
1165

UNCOV
1166
        const unsigned int *pTriangle = Q_NULLPTR;
×
1167
        Vertex *pVertex0 = Q_NULLPTR;
×
1168
        Vertex *pVertex1 = Q_NULLPTR;
×
1169
        Vertex *pVertex2 = Q_NULLPTR;
×
1170
        float edge1[3] = {0.0f, 0.0f, 0.0f};
×
1171
        float edge2[3] = {0.0f, 0.0f, 0.0f};
×
1172
        float texEdge1[2] = {0.0f, 0.0f};
×
1173
        float texEdge2[2] = {0.0f, 0.0f};
×
1174
        float tangent[3] = {0.0f, 0.0f, 0.0f};
×
1175
        float bitangent[3] = {0.0f, 0.0f, 0.0f};
×
1176
        const int totalVertices = m_vertices.size();
×
1177
        const int totalTriangles = m_indices.size() / 3;
×
1178

1179
        // Initialize all the vertex tangents and bitangents.
1180
        for (int i=0; i<totalVertices; ++i)
×
1181
        {
1182
                pVertex0 = &m_vertices[i];
×
1183

UNCOV
1184
                pVertex0->tangent[0] = 0.0f;
×
1185
                pVertex0->tangent[1] = 0.0f;
×
UNCOV
1186
                pVertex0->tangent[2] = 0.0f;
×
1187
                pVertex0->tangent[3] = 0.0f;
×
1188

1189
                pVertex0->bitangent[0] = 0.0f;
×
1190
                pVertex0->bitangent[1] = 0.0f;
×
1191
                pVertex0->bitangent[2] = 0.0f;
×
1192
        }
1193

1194
        // Calculate the vertex tangents and bitangents.
1195
        for (int i=0; i<totalTriangles; ++i)
×
1196
        {
UNCOV
1197
                pTriangle = &m_indices.at(i*3);
×
1198

UNCOV
1199
                pVertex0 = &m_vertices[static_cast<int>(pTriangle[0])];
×
1200
                pVertex1 = &m_vertices[static_cast<int>(pTriangle[1])];
×
UNCOV
1201
                pVertex2 = &m_vertices[static_cast<int>(pTriangle[2])];
×
1202

1203
                // Calculate the triangle face tangent and bitangent.
1204

1205
                edge1[0] = static_cast<float>(pVertex1->position[0] - pVertex0->position[0]);
×
1206
                edge1[1] = static_cast<float>(pVertex1->position[1] - pVertex0->position[1]);
×
UNCOV
1207
                edge1[2] = static_cast<float>(pVertex1->position[2] - pVertex0->position[2]);
×
1208

UNCOV
1209
                edge2[0] = static_cast<float>(pVertex2->position[0] - pVertex0->position[0]);
×
1210
                edge2[1] = static_cast<float>(pVertex2->position[1] - pVertex0->position[1]);
×
1211
                edge2[2] = static_cast<float>(pVertex2->position[2] - pVertex0->position[2]);
×
1212

UNCOV
1213
                texEdge1[0] = pVertex1->texCoord[0] - pVertex0->texCoord[0];
×
1214
                texEdge1[1] = pVertex1->texCoord[1] - pVertex0->texCoord[1];
×
1215

1216
                texEdge2[0] = pVertex2->texCoord[0] - pVertex0->texCoord[0];
×
UNCOV
1217
                texEdge2[1] = pVertex2->texCoord[1] - pVertex0->texCoord[1];
×
1218

1219
                float det = texEdge1[0]*texEdge2[1] - texEdge2[0]*texEdge1[1];
×
1220

1221
                if (fabs(det) < 1e-6f)
×
1222
                {
UNCOV
1223
                        tangent[0] = 1.0f;
×
1224
                        tangent[1] = 0.0f;
×
UNCOV
1225
                        tangent[2] = 0.0f;
×
1226

UNCOV
1227
                        bitangent[0] = 0.0f;
×
1228
                        bitangent[1] = 1.0f;
×
1229
                        bitangent[2] = 0.0f;
×
1230
                }
1231
                else
1232
                {
1233
                        det = 1.0f / det;
×
1234

UNCOV
1235
                        tangent[0] = (texEdge2[1]*edge1[0] - texEdge1[1]*edge2[0])*det;
×
UNCOV
1236
                        tangent[1] = (texEdge2[1]*edge1[1] - texEdge1[1]*edge2[1])*det;
×
UNCOV
1237
                        tangent[2] = (texEdge2[1]*edge1[2] - texEdge1[1]*edge2[2])*det;
×
1238

UNCOV
1239
                        bitangent[0] = (-texEdge2[0]*edge1[0] + texEdge1[0]*edge2[0])*det;
×
1240
                        bitangent[1] = (-texEdge2[0]*edge1[1] + texEdge1[0]*edge2[1])*det;
×
1241
                        bitangent[2] = (-texEdge2[0]*edge1[2] + texEdge1[0]*edge2[2])*det;
×
1242
                }
1243

1244
                // Accumulate the tangents and bitangents.
1245

1246
                pVertex0->tangent[0] += tangent[0];
×
UNCOV
1247
                pVertex0->tangent[1] += tangent[1];
×
UNCOV
1248
                pVertex0->tangent[2] += tangent[2];
×
UNCOV
1249
                pVertex0->bitangent[0] += bitangent[0];
×
UNCOV
1250
                pVertex0->bitangent[1] += bitangent[1];
×
1251
                pVertex0->bitangent[2] += bitangent[2];
×
1252

1253
                pVertex1->tangent[0] += tangent[0];
×
1254
                pVertex1->tangent[1] += tangent[1];
×
1255
                pVertex1->tangent[2] += tangent[2];
×
1256
                pVertex1->bitangent[0] += bitangent[0];
×
UNCOV
1257
                pVertex1->bitangent[1] += bitangent[1];
×
1258
                pVertex1->bitangent[2] += bitangent[2];
×
1259

1260
                pVertex2->tangent[0] += tangent[0];
×
1261
                pVertex2->tangent[1] += tangent[1];
×
1262
                pVertex2->tangent[2] += tangent[2];
×
1263
                pVertex2->bitangent[0] += bitangent[0];
×
UNCOV
1264
                pVertex2->bitangent[1] += bitangent[1];
×
1265
                pVertex2->bitangent[2] += bitangent[2];
×
1266
        }
1267

1268
        // Orthogonalize and normalize the vertex tangents.
1269
        for (int i=0; i<totalVertices; ++i)
×
1270
        {
UNCOV
1271
                pVertex0 = &m_vertices[i];
×
1272

1273
                // Gram-Schmidt orthogonalize tangent with normal.
1274

UNCOV
1275
                float nDotT = pVertex0->normal[0]*pVertex0->tangent[0] +
×
1276
                              pVertex0->normal[1]*pVertex0->tangent[1] +
×
UNCOV
1277
                              pVertex0->normal[2]*pVertex0->tangent[2];
×
1278

UNCOV
1279
                pVertex0->tangent[0] -= pVertex0->normal[0]*nDotT;
×
1280
                pVertex0->tangent[1] -= pVertex0->normal[1]*nDotT;
×
1281
                pVertex0->tangent[2] -= pVertex0->normal[2]*nDotT;
×
1282

1283
                // Normalize the tangent.
1284

1285
                float invlength = 1.0f / sqrtf(pVertex0->tangent[0]*pVertex0->tangent[0] +
×
1286
                                               pVertex0->tangent[1]*pVertex0->tangent[1] +
×
UNCOV
1287
                                               pVertex0->tangent[2]*pVertex0->tangent[2]);
×
1288

UNCOV
1289
                pVertex0->tangent[0] *= invlength;
×
1290
                pVertex0->tangent[1] *= invlength;
×
1291
                pVertex0->tangent[2] *= invlength;
×
1292

1293
                // Calculate the handedness of the local tangent space.
1294
                // The bitangent vector is the cross product between the triangle face
1295
                // normal vector and the calculated tangent vector. The resulting
1296
                // bitangent vector should be the same as the bitangent vector
1297
                // calculated from the set of linear equations above. If they point in
1298
                // different directions then we need to invert the cross product
1299
                // calculated bitangent vector. We store this scalar multiplier in the
1300
                // tangent vector's 'w' component so that the correct bitangent vector
1301
                // can be generated in the normal mapping shader's vertex shader.
1302
                //
1303
                // Normal maps have a left handed coordinate system with the origin
1304
                // located at the top left of the normal map texture. The x coordinates
1305
                // run horizontally from left to right. The y coordinates run
1306
                // vertically from top to bottom. The z coordinates run out of the
1307
                // normal map texture towards the viewer. Our handedness calculations
1308
                // must take this fact into account as well so that the normal mapping
1309
                // shader's vertex shader will generate the correct bitangent vectors.
1310
                // Some normal map authoring tools such as Crazybump
1311
                // (http://www.crazybump.com/) includes options to allow you to control
1312
                // the orientation of the normal map normal's y-axis.
1313

UNCOV
1314
                bitangent[0] = (pVertex0->normal[1]*pVertex0->tangent[2]) -
×
UNCOV
1315
                               (pVertex0->normal[2]*pVertex0->tangent[1]);
×
UNCOV
1316
                bitangent[1] = (pVertex0->normal[2]*pVertex0->tangent[0]) -
×
UNCOV
1317
                               (pVertex0->normal[0]*pVertex0->tangent[2]);
×
UNCOV
1318
                bitangent[2] = (pVertex0->normal[0]*pVertex0->tangent[1]) -
×
1319
                               (pVertex0->normal[1]*pVertex0->tangent[0]);
×
1320

1321
                float bDotB = bitangent[0]*pVertex0->bitangent[0] +
×
1322
                              bitangent[1]*pVertex0->bitangent[1] +
×
1323
                              bitangent[2]*pVertex0->bitangent[2];
×
1324

UNCOV
1325
                pVertex0->tangent[3] = (bDotB < 0.0f) ? 1.0f : -1.0f;
×
1326

1327
                pVertex0->bitangent[0] = bitangent[0];
×
1328
                pVertex0->bitangent[1] = bitangent[1];
×
UNCOV
1329
                pVertex0->bitangent[2] = bitangent[2];
×
1330
        }
UNCOV
1331
}
×
1332

1333
void StelOBJ::generateAABB()
×
1334
{
1335
        //calculate AABB and centroid for each object
1336
        Vec3d accCentroid(0.);
×
UNCOV
1337
        m_bbox.reset();
×
1338
        for(int i =0;i<m_objects.size();++i)
×
1339
        {
UNCOV
1340
                Vec3d centr(0.);
×
1341
                Object& o = m_objects[i];
×
1342

1343
                o.postprocess(*this,centr);
×
UNCOV
1344
                m_bbox.expand(o.boundingbox);
×
1345
                accCentroid+=centr;
×
1346
        }
1347

1348
        m_centroid = (accCentroid / m_objects.size()).toVec3f();
×
1349
}
×
1350

UNCOV
1351
void StelOBJ::performPostProcessing(bool genNormals)
×
1352
{
1353
        QElapsedTimer timer;
×
1354
        timer.start();
×
1355

1356
        //if no normals have been read at all, or if normals are broken in the OBJ file and flagged as such in the scenery3d.ini, (re-)generate them (we do not support smoothing groups at the time, so this is quite simple)
UNCOV
1357
        if(genNormals)
×
1358
        {
1359
                generateNormals();
×
UNCOV
1360
                qCDebug(stelOBJ)<<"Normals calculated in"<<timer.restart()<<"ms";
×
1361
        }
1362

1363
        //generate tangent data
1364
        generateTangents();
×
1365
        qCDebug(stelOBJ())<<"Tangents calculated in"<<timer.restart()<<"ms";
×
1366

UNCOV
1367
        generateAABB();
×
UNCOV
1368
        qCDebug(stelOBJ)<<"AABBs/Centroids calculated in"<<timer.elapsed()<<"ms";
×
1369
        qCDebug(stelOBJ)<<"Centroid is at "<<m_centroid;
×
1370
}
×
1371

1372
StelOBJ::ShortIndexList StelOBJ::getShortIndexList() const
×
1373
{
1374
        QElapsedTimer timer;
×
1375
        timer.start();
×
1376

1377
        ShortIndexList ret;
×
UNCOV
1378
        if(!canUseShortIndices())
×
1379
        {
1380
                qCWarning(stelOBJ)<<"Cannot use short indices for OBJ data, it has"<<m_vertices.size()<<"vertices";
×
UNCOV
1381
                return ret;
×
1382
        }
1383

UNCOV
1384
        ret.reserve(m_indices.size());
×
1385
        for(int i =0;i<m_indices.size();++i)
×
1386
        {
UNCOV
1387
                ret.append(static_cast<unsigned short>(m_indices.at(i)));
×
1388
        }
1389

1390
        qCDebug(stelOBJ)<<"Indices converted to short in"<<timer.elapsed()<<"ms";
×
UNCOV
1391
        return ret;
×
1392
}
×
1393

UNCOV
1394
void StelOBJ::scale(double factor)
×
1395
{
1396
        QElapsedTimer timer;
×
1397
        timer.start();
×
1398

1399
        for(int i = 0;i<m_vertices.size();++i)
×
1400
        {
1401
                GLfloat* dat = m_vertices[i].position;
×
1402
                dat[0] *= static_cast<GLfloat>(factor);
×
UNCOV
1403
                dat[1] *= static_cast<GLfloat>(factor);
×
1404
                dat[2] *= static_cast<GLfloat>(factor);
×
1405
        }
1406

1407
        //AABBs must be recalculated
1408
        generateAABB();
×
1409
        qCDebug(stelOBJ)<<"Scaling done in"<<timer.elapsed()<<"ms";
×
UNCOV
1410
}
×
1411

UNCOV
1412
void StelOBJ::transform(const QMatrix4x4 &mat, bool onlyPosition)
×
1413
{
1414
        //matrix for normals/tangents
1415
        QMatrix3x3 normalMat = mat.normalMatrix();
×
1416

1417
        //Transform all vertices and normals by mat
UNCOV
1418
        for(int i=0; i<m_vertices.size(); ++i)
×
1419
        {
1420
                Vertex& pVertex = m_vertices[i];
×
1421

UNCOV
1422
                QVector3D tf = mat.map(QVector3D(pVertex.position[0], pVertex.position[1], pVertex.position[2]));
×
1423
                std::copy(&tf[0],&tf[0]+3,pVertex.position);
×
1424

1425
                if(!onlyPosition)
×
1426
                {
1427
                        tf = normalMat * QVector3D(pVertex.normal[0], pVertex.normal[1], pVertex.normal[2]);
×
1428
                        pVertex.normal[0] = tf.x();
×
UNCOV
1429
                        pVertex.normal[1] = tf.y();
×
1430
                        pVertex.normal[2] = tf.z();
×
1431

1432
                        tf = normalMat * QVector3D(pVertex.tangent[0], pVertex.tangent[1], pVertex.tangent[2]);
×
1433
                        pVertex.tangent[0] = tf.x();
×
1434
                        pVertex.tangent[1] = tf.y();
×
1435
                        pVertex.tangent[2] = tf.z();
×
1436

1437
                        tf = normalMat * QVector3D(pVertex.bitangent[0], pVertex.bitangent[1], pVertex.bitangent[2]);
×
1438
                        pVertex.bitangent[0] = tf.x();
×
1439
                        pVertex.bitangent[1] = tf.y();
×
1440
                        pVertex.bitangent[2] = tf.z();
×
1441
                }
1442
        }
1443

1444
        //Update bounding box in case it changed
1445
        generateAABB();
×
UNCOV
1446
}
×
1447

UNCOV
1448
void StelOBJ::splitVertexData(V3Vec *position,
×
1449
                              V2Vec *texCoord,
1450
                              V3Vec *normal,
1451
                              V3Vec *tangent,
1452
                              V3Vec *bitangent) const
1453
{
UNCOV
1454
        QElapsedTimer timer;
×
UNCOV
1455
        timer.start();
×
1456

UNCOV
1457
        const int size = m_vertices.size();
×
1458
        //resize arrays
1459
        if(position)
×
1460
                position->resize(size);
×
UNCOV
1461
        if(texCoord)
×
1462
                texCoord->resize(size);
×
UNCOV
1463
        if(normal)
×
1464
                normal->resize(size);
×
1465
        if(tangent)
×
1466
                tangent->resize(size);
×
1467
        if(bitangent)
×
1468
                bitangent->resize(size);
×
1469

1470
        for(int i = 0;i<size;++i)
×
1471
        {
1472
                const Vertex& vtx = m_vertices.at(i);
×
1473
                if(position)
×
UNCOV
1474
                        (*position)[i] = Vec3f(vtx.position);
×
1475
                if(texCoord)
×
UNCOV
1476
                        (*texCoord)[i] = Vec2f(vtx.texCoord);
×
1477
                if(normal)
×
1478
                        (*normal)[i] = Vec3f(vtx.normal);
×
1479
                if(tangent)
×
1480
                        (*tangent)[i] = Vec3f(vtx.tangent);
×
1481
                if(bitangent)
×
1482
                        (*bitangent)[i] = Vec3f(vtx.bitangent);
×
1483
        }                
1484

1485
        qCDebug(stelOBJ)<<"Vertex data split in "<<timer.elapsed()<<"ms";
×
1486
}
×
1487

UNCOV
1488
void StelOBJ::clearVertexData()
×
1489
{
1490
        m_vertices.clear();
×
1491
}
×
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