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

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