| 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 | |
| 16 | namespace Qt3DRender { |
| 17 | |
| 18 | using namespace Qt3DCore; |
| 19 | |
| 20 | Q_LOGGING_CATEGORY(BaseGeometryLoaderLog, "Qt3D.BaseGeometryLoader" , QtWarningMsg) |
| 21 | |
| 22 | BaseGeometryLoader::BaseGeometryLoader() |
| 23 | : m_loadTextureCoords(true) |
| 24 | , m_generateTangents(true) |
| 25 | , m_centerMesh(false) |
| 26 | , m_geometry(nullptr) |
| 27 | { |
| 28 | } |
| 29 | |
| 30 | Qt3DCore::QGeometry *BaseGeometryLoader::geometry() const |
| 31 | { |
| 32 | return m_geometry; |
| 33 | } |
| 34 | |
| 35 | bool 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 | |
| 61 | void 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 | |
| 86 | void 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 | |
| 174 | void 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 | |
| 230 | void 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 | |
| 247 | QT_END_NAMESPACE |
| 248 | |
| 249 | #include "moc_basegeometryloader_p.cpp" |
| 250 | |