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