1// Copyright (C) 2017 The Qt Company Ltd and/or its subsidiary(-ies).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "objgeometryloader.h"
5
6#include <QtCore/QHash>
7#include <QtCore/QLoggingCategory>
8#include <QtCore/QRegularExpression>
9#include <QtCore/QIODevice>
10
11QT_BEGIN_NAMESPACE
12
13namespace Qt3DRender {
14
15Q_LOGGING_CATEGORY(ObjGeometryLoaderLog, "Qt3D.ObjGeometryLoader", QtWarningMsg)
16
17static void addFaceVertex(const FaceIndices &faceIndices,
18 QList<FaceIndices> &faceIndexVector,
19 QHash<FaceIndices, unsigned int> &faceIndexMap);
20
21inline uint qHash(const FaceIndices &faceIndices)
22{
23 return faceIndices.positionIndex
24 + 10 * faceIndices.texCoordIndex
25 + 100 * faceIndices.normalIndex;
26}
27
28bool ObjGeometryLoader::doLoad(QIODevice *ioDev, const QString &subMesh)
29{
30 // Parse faces taking into account each vertex in a face can index different indices
31 // for the positions, normals and texture coords;
32 // Generate unique vertices (in OpenGL parlance) and output to points, texCoords,
33 // normals and calculate mapping from faces to unique indices
34 QList<QVector3D> positions;
35 QList<QVector3D> normals;
36 QList<QVector2D> texCoords;
37 QHash<FaceIndices, unsigned int> faceIndexMap;
38 QList<FaceIndices> faceIndexVector;
39
40 bool skipping = false;
41 int positionsOffset = 0;
42 int normalsOffset = 0;
43 int texCoordsOffset = 0;
44
45 QRegularExpression subMeshMatch(subMesh);
46 if (!subMeshMatch.isValid())
47 subMeshMatch.setPattern(QLatin1String("^(") + subMesh + QLatin1String(")$"));
48 Q_ASSERT(subMeshMatch.isValid());
49
50 char lineBuffer[1024];
51 const char *line;
52 QByteArray longLine;
53 while (!ioDev->atEnd()) {
54 // try to read into lineBuffer first, if the line fits (common case) we can do this without expensive allocations
55 // if not, fall back to dynamically allocated QByteArrays
56 auto lineSize = ioDev->readLine(data: lineBuffer, maxlen: sizeof(lineBuffer));
57 if (lineSize == sizeof(lineBuffer) - 1 && lineBuffer[lineSize - 1] != '\n') {
58 longLine = QByteArray(lineBuffer, lineSize);
59 longLine += ioDev->readLine();
60 line = longLine.constData();
61 lineSize = longLine.size();
62 } else {
63 line = lineBuffer;
64 }
65
66 if (lineSize > 0 && line[0] != '#') {
67 if (line[lineSize - 1] == '\n')
68 --lineSize; // chop newline
69 if (lineSize <= 0)
70 continue;
71
72 if (line[lineSize - 1] == '\r')
73 --lineSize; // chop newline also for CRLF format
74 if (lineSize <= 0)
75 continue;
76
77 while (lineSize > 0 && (line[lineSize - 1] == ' ' || line[lineSize - 1] == '\t')) {
78 --lineSize; // chop trailing spaces
79 }
80 if (lineSize <= 0)
81 continue;
82
83 const ByteArraySplitter tokens(line, line + lineSize, ' ', Qt::SkipEmptyParts);
84
85 if (qstrncmp(str1: tokens.charPtrAt(index: 0), str2: "v ", len: 2) == 0) {
86 if (tokens.size() < 4) {
87 qCWarning(ObjGeometryLoaderLog) << "Unsupported number of components in vertex";
88 } else {
89 if (!skipping) {
90 const float x = tokens.floatAt(index: 1);
91 const float y = tokens.floatAt(index: 2);
92 const float z = tokens.floatAt(index: 3);
93 positions.append(t: QVector3D(x, y, z));
94 } else {
95 positionsOffset++;
96 }
97 }
98 } else if (m_loadTextureCoords && qstrncmp(str1: tokens.charPtrAt(index: 0), str2: "vt ", len: 3) == 0) {
99 if (tokens.size() < 3) {
100 qCWarning(ObjGeometryLoaderLog) << "Unsupported number of components in texture coordinate";
101 } else {
102 if (!skipping) {
103 // Process texture coordinate
104 const float s = tokens.floatAt(index: 1);
105 const float t = tokens.floatAt(index: 2);
106 texCoords.append(t: QVector2D(s, t));
107 } else {
108 ++texCoordsOffset;
109 }
110 }
111 } else if (qstrncmp(str1: tokens.charPtrAt(index: 0), str2: "vn ", len: 3) == 0) {
112 if (tokens.size() < 4) {
113 qCWarning(ObjGeometryLoaderLog) << "Unsupported number of components in vertex normal";
114 } else {
115 if (!skipping) {
116 const float x = tokens.floatAt(index: 1);
117 const float y = tokens.floatAt(index: 2);
118 const float z = tokens.floatAt(index: 3);
119 normals.append(t: QVector3D(x, y, z));
120 } else {
121 ++normalsOffset;
122 }
123 }
124 } else if (!skipping && tokens.size() >= 4 && qstrncmp(str1: tokens.charPtrAt(index: 0), str2: "f ", len: 2) == 0) {
125 // Process face
126 int faceVertices = tokens.size() - 1;
127
128 QVarLengthArray<FaceIndices, 4> face; // try to avoid allocations in the common case of triangulated data
129 face.reserve(sz: faceVertices);
130
131 for (int i = 0; i < faceVertices; i++) {
132 FaceIndices faceIndices;
133 const ByteArraySplitter indices = tokens.splitterAt(index: i + 1, delimiter: '/', splitBehavior: Qt::KeepEmptyParts);
134 switch (indices.size()) {
135 case 3:
136 faceIndices.normalIndex = indices.intAt(index: 2) - 1 - normalsOffset; // fall through
137 Q_FALLTHROUGH();
138 case 2:
139 faceIndices.texCoordIndex = indices.intAt(index: 1) - 1 - texCoordsOffset; // fall through
140 Q_FALLTHROUGH();
141 case 1:
142 faceIndices.positionIndex = indices.intAt(index: 0) - 1 - positionsOffset;
143 break;
144 default:
145 qCWarning(ObjGeometryLoaderLog) << "Unsupported number of indices in face element";
146 }
147
148 face.append(t: faceIndices);
149 }
150
151 // If number of edges in face is greater than 3,
152 // decompose into triangles as a triangle fan.
153 FaceIndices v0 = face[0];
154 FaceIndices v1 = face[1];
155 FaceIndices v2 = face[2];
156
157 // First face
158 addFaceVertex(faceIndices: v0, faceIndexVector, faceIndexMap);
159 addFaceVertex(faceIndices: v1, faceIndexVector, faceIndexMap);
160 addFaceVertex(faceIndices: v2, faceIndexVector, faceIndexMap);
161
162 for (int i = 3; i < face.size(); ++i) {
163 v1 = v2;
164 v2 = face[i];
165 addFaceVertex(faceIndices: v0, faceIndexVector, faceIndexMap);
166 addFaceVertex(faceIndices: v1, faceIndexVector, faceIndexMap);
167 addFaceVertex(faceIndices: v2, faceIndexVector, faceIndexMap);
168 }
169
170 // end of face
171 } else if (qstrncmp(str1: tokens.charPtrAt(index: 0), str2: "o ", len: 2) == 0) {
172 if (tokens.size() < 2) {
173 qCWarning(ObjGeometryLoaderLog) << "Missing submesh name";
174 } else {
175 if (!subMesh.isEmpty() ) {
176 const QString objName = tokens.stringAt(index: 1);
177 QRegularExpressionMatch match = subMeshMatch.match(subject: objName);
178 skipping = !match.hasMatch();
179 }
180 }
181 }
182 } // empty input line
183 } // while (!ioDev->atEnd())
184
185 // Iterate over the faceIndexMap and pull out pos, texCoord and normal data
186 // thereby generating unique vertices of data (by OpenGL definition)
187 const qsizetype vertexCount = faceIndexMap.size();
188 const bool hasTexCoords = !texCoords.isEmpty();
189 const bool hasNormals = !normals.isEmpty();
190
191 m_points.resize(new_size: vertexCount);
192 m_texCoords.clear();
193 if (hasTexCoords)
194 m_texCoords.resize(new_size: vertexCount);
195 m_normals.clear();
196 if (hasNormals)
197 m_normals.resize(new_size: vertexCount);
198
199 for (auto it = faceIndexMap.cbegin(), endIt = faceIndexMap.cend(); it != endIt; ++it) {
200 const uint positionIndex = it.key().positionIndex;
201 const uint texCoordIndex = it.key().texCoordIndex;
202 const uint normalIndex = it.key().normalIndex;
203
204 m_points[it.value()] = (positionIndex < uint(positions.size())) ? positions[positionIndex] : QVector3D();
205 if (hasTexCoords)
206 m_texCoords[it.value()] = (texCoordIndex < uint(texCoords.size())) ? texCoords[texCoordIndex] : QVector2D();
207 if (hasNormals)
208 m_normals[it.value()] = (normalIndex < uint(normals.size())) ? normals[normalIndex] : QVector3D();
209 }
210
211 // Now iterate over the face indices and lookup the unique vertex index
212 const qsizetype indexCount = faceIndexVector.size();
213 m_indices.clear();
214 m_indices.reserve(n: indexCount);
215 for (const FaceIndices &faceIndices : std::as_const(t&: faceIndexVector)) {
216 const unsigned int i = faceIndexMap.value(key: faceIndices);
217 m_indices.push_back(x: i);
218 }
219
220 return true;
221}
222
223static void addFaceVertex(const FaceIndices &faceIndices,
224 QList<FaceIndices> &faceIndexVector,
225 QHash<FaceIndices, unsigned int> &faceIndexMap)
226{
227 if (faceIndices.positionIndex != std::numeric_limits<unsigned int>::max()) {
228 faceIndexVector.append(t: faceIndices);
229 if (!faceIndexMap.contains(key: faceIndices))
230 faceIndexMap.insert(key: faceIndices, value: faceIndexMap.size());
231 } else {
232 qCWarning(ObjGeometryLoaderLog) << "Missing position index";
233 }
234}
235
236} // namespace Qt3DRender
237
238QT_END_NAMESPACE
239

source code of qt3d/src/plugins/geometryloaders/default/objgeometryloader.cpp