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