1// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
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 "basegeometryloader_p.h"
5
6#include <Qt3DCore/qattribute.h>
7#include <Qt3DCore/qbuffer.h>
8#include <Qt3DCore/qgeometry.h>
9
10#include <Qt3DRender/private/qaxisalignedboundingbox_p.h>
11#include <Qt3DRender/private/renderlogging_p.h>
12
13QT_BEGIN_NAMESPACE
14
15
16namespace Qt3DRender {
17
18using namespace Qt3DCore;
19
20Q_LOGGING_CATEGORY(BaseGeometryLoaderLog, "Qt3D.BaseGeometryLoader", QtWarningMsg)
21
22BaseGeometryLoader::BaseGeometryLoader()
23 : m_loadTextureCoords(true)
24 , m_generateTangents(true)
25 , m_centerMesh(false)
26 , m_geometry(nullptr)
27{
28}
29
30Qt3DCore::QGeometry *BaseGeometryLoader::geometry() const
31{
32 return m_geometry;
33}
34
35bool BaseGeometryLoader::load(QIODevice *ioDev, const QString &subMesh)
36{
37 if (!doLoad(ioDev, subMesh))
38 return false;
39
40 if (m_normals.empty())
41 generateAveragedNormals(points: m_points, normals&: m_normals, faces: m_indices);
42
43 if (m_generateTangents && !m_texCoords.empty())
44 generateTangents(points: m_points, normals: m_normals, faces: m_indices, texCoords: m_texCoords, tangents&: m_tangents);
45
46 if (m_centerMesh)
47 center(points&: m_points);
48
49 qCDebug(BaseGeometryLoaderLog) << "Loaded mesh:";
50 qCDebug(BaseGeometryLoaderLog) << " " << m_points.size() << "points";
51 qCDebug(BaseGeometryLoaderLog) << " " << m_indices.size() / 3 << "triangles.";
52 qCDebug(BaseGeometryLoaderLog) << " " << m_normals.size() << "normals";
53 qCDebug(BaseGeometryLoaderLog) << " " << m_tangents.size() << "tangents ";
54 qCDebug(BaseGeometryLoaderLog) << " " << m_texCoords.size() << "texture coordinates.";
55
56 generateGeometry();
57
58 return true;
59}
60
61void BaseGeometryLoader::generateAveragedNormals(const std::vector<QVector3D> &points,
62 std::vector<QVector3D> &normals,
63 const std::vector<unsigned int> &faces) const
64{
65 for (size_t i = 0; i < points.size(); ++i)
66 normals.push_back(x: QVector3D());
67
68 for (size_t i = 0; i < faces.size(); i += 3) {
69 const QVector3D &p1 = points[ faces[i] ];
70 const QVector3D &p2 = points[ faces[i+1] ];
71 const QVector3D &p3 = points[ faces[i+2] ];
72
73 const QVector3D a = p2 - p1;
74 const QVector3D b = p3 - p1;
75 const QVector3D n = QVector3D::crossProduct(v1: a, v2: b).normalized();
76
77 normals[ faces[i] ] += n;
78 normals[ faces[i+1] ] += n;
79 normals[ faces[i+2] ] += n;
80 }
81
82 for (size_t i = 0; i < normals.size(); ++i)
83 normals[i].normalize();
84}
85
86void BaseGeometryLoader::generateGeometry()
87{
88 QByteArray bufferBytes;
89 const uint count = uint(m_points.size());
90 const quint32 elementSize = 3 + (hasTextureCoordinates() ? 2 : 0)
91 + (hasNormals() ? 3 : 0)
92 + (hasTangents() ? 4 : 0);
93 const quint32 stride = elementSize * sizeof(float);
94 bufferBytes.resize(size: stride * count);
95 float *fptr = reinterpret_cast<float*>(bufferBytes.data());
96
97 for (size_t index = 0; index < count; ++index) {
98 *fptr++ = m_points.at(n: index).x();
99 *fptr++ = m_points.at(n: index).y();
100 *fptr++ = m_points.at(n: index).z();
101
102 if (hasTextureCoordinates()) {
103 *fptr++ = m_texCoords.at(n: index).x();
104 *fptr++ = m_texCoords.at(n: index).y();
105 }
106
107 if (hasNormals()) {
108 *fptr++ = m_normals.at(n: index).x();
109 *fptr++ = m_normals.at(n: index).y();
110 *fptr++ = m_normals.at(n: index).z();
111 }
112
113 if (hasTangents()) {
114 *fptr++ = m_tangents.at(n: index).x();
115 *fptr++ = m_tangents.at(n: index).y();
116 *fptr++ = m_tangents.at(n: index).z();
117 *fptr++ = m_tangents.at(n: index).w();
118 }
119 } // of buffer filling loop
120
121 auto *buf = new Qt3DCore::QBuffer();
122 buf->setData(bufferBytes);
123
124 if (m_geometry)
125 qDebug(catFunc: BaseGeometryLoaderLog, msg: "Existing geometry instance getting overridden.");
126 m_geometry = new QGeometry();
127
128 QAttribute *positionAttribute = new QAttribute(buf, QAttribute::defaultPositionAttributeName(), QAttribute::Float, 3, count, 0, stride);
129 m_geometry->addAttribute(attribute: positionAttribute);
130 quint32 offset = sizeof(float) * 3;
131
132 if (hasTextureCoordinates()) {
133 QAttribute *texCoordAttribute = new QAttribute(buf, QAttribute::defaultTextureCoordinateAttributeName(), QAttribute::Float, 2, count, offset, stride);
134 m_geometry->addAttribute(attribute: texCoordAttribute);
135 offset += sizeof(float) * 2;
136 }
137
138 if (hasNormals()) {
139 QAttribute *normalAttribute = new QAttribute(buf, QAttribute::defaultNormalAttributeName(), QAttribute::Float, 3, count, offset, stride);
140 m_geometry->addAttribute(attribute: normalAttribute);
141 offset += sizeof(float) * 3;
142 }
143
144 if (hasTangents()) {
145 QAttribute *tangentAttribute = new QAttribute(buf, QAttribute::defaultTangentAttributeName(),QAttribute::Float, 4, count, offset, stride);
146 m_geometry->addAttribute(attribute: tangentAttribute);
147 offset += sizeof(float) * 4;
148 }
149
150 QByteArray indexBytes;
151 QAttribute::VertexBaseType ty;
152 if (m_indices.size() < 65536) {
153 // we can use USHORT
154 ty = QAttribute::UnsignedShort;
155 indexBytes.resize(size: m_indices.size() * sizeof(quint16));
156 quint16 *usptr = reinterpret_cast<quint16*>(indexBytes.data());
157 for (int i = 0; i < int(m_indices.size()); ++i)
158 *usptr++ = static_cast<quint16>(m_indices.at(n: i));
159 } else {
160 // use UINT - no conversion needed, but let's ensure int is 32-bit!
161 ty = QAttribute::UnsignedInt;
162 Q_ASSERT(sizeof(int) == sizeof(quint32));
163 indexBytes.resize(size: m_indices.size() * sizeof(quint32));
164 memcpy(dest: indexBytes.data(), src: reinterpret_cast<const char*>(m_indices.data()), n: indexBytes.size());
165 }
166
167 auto *indexBuffer = new Qt3DCore::QBuffer();
168 indexBuffer->setData(indexBytes);
169 QAttribute *indexAttribute = new QAttribute(indexBuffer, ty, 1, uint(m_indices.size()));
170 indexAttribute->setAttributeType(QAttribute::IndexAttribute);
171 m_geometry->addAttribute(attribute: indexAttribute);
172}
173
174void BaseGeometryLoader::generateTangents(const std::vector<QVector3D> &points,
175 const std::vector<QVector3D> &normals,
176 const std::vector<unsigned int> &faces,
177 const std::vector<QVector2D> &texCoords,
178 std::vector<QVector4D> &tangents) const
179{
180 tangents.clear();
181 std::vector<QVector3D> tan1Accum;
182 std::vector<QVector3D> tan2Accum;
183
184 tan1Accum.resize(new_size: points.size());
185 tan2Accum.resize(new_size: points.size());
186 tangents.resize(new_size: points.size());
187
188 // Compute the tangent vector
189 for (size_t i = 0; i < faces.size(); i += 3) {
190 const QVector3D &p1 = points[ faces[i] ];
191 const QVector3D &p2 = points[ faces[i+1] ];
192 const QVector3D &p3 = points[ faces[i+2] ];
193
194 const QVector2D &tc1 = texCoords[ faces[i] ];
195 const QVector2D &tc2 = texCoords[ faces[i+1] ];
196 const QVector2D &tc3 = texCoords[ faces[i+2] ];
197
198 const QVector3D q1 = p2 - p1;
199 const QVector3D q2 = p3 - p1;
200 const float s1 = tc2.x() - tc1.x(), s2 = tc3.x() - tc1.x();
201 const float t1 = tc2.y() - tc1.y(), t2 = tc3.y() - tc1.y();
202 const float r = 1.0f / (s1 * t2 - s2 * t1);
203 const QVector3D tan1((t2 * q1.x() - t1 * q2.x()) * r,
204 (t2 * q1.y() - t1 * q2.y()) * r,
205 (t2 * q1.z() - t1 * q2.z()) * r);
206 const QVector3D tan2((s1 * q2.x() - s2 * q1.x()) * r,
207 (s1 * q2.y() - s2 * q1.y()) * r,
208 (s1 * q2.z() - s2 * q1.z()) * r);
209 tan1Accum[ faces[i] ] += tan1;
210 tan1Accum[ faces[i+1] ] += tan1;
211 tan1Accum[ faces[i+2] ] += tan1;
212 tan2Accum[ faces[i] ] += tan2;
213 tan2Accum[ faces[i+1] ] += tan2;
214 tan2Accum[ faces[i+2] ] += tan2;
215 }
216
217 for (size_t i = 0; i < points.size(); ++i) {
218 const QVector3D &n = normals[i];
219 const QVector3D &t1 = tan1Accum[i];
220 const QVector3D &t2 = tan2Accum[i];
221
222 // Gram-Schmidt orthogonalize
223 tangents[i] = QVector4D(QVector3D(t1 - QVector3D::dotProduct(v1: n, v2: t1) * n).normalized(), 0.0f);
224
225 // Store handedness in w
226 tangents[i].setW((QVector3D::dotProduct(v1: QVector3D::crossProduct(v1: n, v2: t1), v2: t2) < 0.0f) ? -1.0f : 1.0f);
227 }
228}
229
230void BaseGeometryLoader::center(std::vector<QVector3D> &points)
231{
232 if (points.empty())
233 return;
234
235 const QAxisAlignedBoundingBox bb(points);
236 const QVector3D center = bb.center();
237
238 // Translate center of the AABB to the origin
239 for (size_t i = 0; i < points.size(); ++i) {
240 QVector3D &point = points[i];
241 point = point - center;
242 }
243}
244
245} // namespace Qt3DRender
246
247QT_END_NAMESPACE
248
249#include "moc_basegeometryloader_p.cpp"
250

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