| 1 | // Copyright (C) 2008-2012 NVIDIA Corporation. |
| 2 | // Copyright (C) 2019 The Qt Company Ltd. |
| 3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
| 4 | |
| 5 | #ifndef QSSGUTILS_H |
| 6 | #define QSSGUTILS_H |
| 7 | |
| 8 | // |
| 9 | // W A R N I N G |
| 10 | // ------------- |
| 11 | // |
| 12 | // This file is not part of the Qt API. It exists purely as an |
| 13 | // implementation detail. This header file may change from version to |
| 14 | // version without notice, or even be removed. |
| 15 | // |
| 16 | // We mean it. |
| 17 | // |
| 18 | |
| 19 | #include <QtQuick3DUtils/private/qtquick3dutilsglobal_p.h> |
| 20 | #include <QtQuick3DUtils/private/qssgdataref_p.h> |
| 21 | |
| 22 | #include <QtGui/QVector2D> |
| 23 | #include <QtGui/QVector3D> |
| 24 | #include <QtGui/QQuaternion> |
| 25 | #include <QtGui/QMatrix3x3> |
| 26 | #include <QtGui/QMatrix4x4> |
| 27 | #include <QtGui/QColor> |
| 28 | |
| 29 | #include <QtCore/qdebug.h> |
| 30 | #include <QtCore/QString> |
| 31 | #include <QtCore/qloggingcategory.h> |
| 32 | #include <QtCore/QIODevice> |
| 33 | #include <QtCore/qmath.h> |
| 34 | |
| 35 | class tst_RotationDataClass; |
| 36 | |
| 37 | QT_BEGIN_NAMESPACE |
| 38 | |
| 39 | namespace QSSGUtils { |
| 40 | |
| 41 | namespace aux { |
| 42 | Q_DECL_CONSTEXPR inline float translateConstantAttenuation(float attenuation) { return attenuation; } |
| 43 | template<int MINATTENUATION = 0, int MAXATTENUATION = 1000> |
| 44 | Q_DECL_CONSTEXPR inline float translateLinearAttenuation(float attenuation) { return qBound(min: float(MINATTENUATION), val: attenuation, max: float(MAXATTENUATION)) * .01f; } |
| 45 | template<int MINATTENUATION = 0, int MAXATTENUATION = 1000> |
| 46 | Q_DECL_CONSTEXPR inline float translateQuadraticAttenuation(float attenuation) { return qBound(min: float(MINATTENUATION), val: attenuation, max: float(MAXATTENUATION)) * .0001f; } |
| 47 | } |
| 48 | |
| 49 | namespace vec2 { |
| 50 | float Q_QUICK3DUTILS_EXPORT magnitude(const QVector2D &v); |
| 51 | } |
| 52 | |
| 53 | namespace vec3 { |
| 54 | inline QVector3D minimum(const QVector3D &v1, const QVector3D &v2) Q_DECL_NOTHROW { return { qMin(a: v1.x(), b: v2.x()), qMin(a: v1.y(), b: v2.y()), qMin(a: v1.z(), b: v2.z()) }; } |
| 55 | inline QVector3D maximum(const QVector3D &v1, const QVector3D &v2) Q_DECL_NOTHROW { return { qMax(a: v1.x(), b: v2.x()), qMax(a: v1.y(), b: v2.y()), qMax(a: v1.z(), b: v2.z()) }; } |
| 56 | bool Q_QUICK3DUTILS_EXPORT isFinite(const QVector3D &v); |
| 57 | float Q_QUICK3DUTILS_EXPORT magnitude(const QVector3D &v); |
| 58 | float Q_QUICK3DUTILS_EXPORT magnitudeSquared(const QVector3D &v); |
| 59 | float Q_QUICK3DUTILS_EXPORT normalize(QVector3D &v); |
| 60 | } |
| 61 | |
| 62 | namespace mat33 { |
| 63 | QVector3D Q_QUICK3DUTILS_EXPORT transform(const QMatrix3x3 &m, const QVector3D &v); |
| 64 | } |
| 65 | |
| 66 | namespace mat44 { |
| 67 | QMatrix3x3 Q_QUICK3DUTILS_EXPORT getUpper3x3(const QMatrix4x4 &m); |
| 68 | void Q_QUICK3DUTILS_EXPORT normalize(QMatrix4x4 &m); |
| 69 | QVector3D Q_QUICK3DUTILS_EXPORT rotate(const QMatrix4x4 &m, const QVector3D &v); |
| 70 | QVector4D Q_QUICK3DUTILS_EXPORT rotate(const QMatrix4x4 &m, const QVector4D &v); |
| 71 | QVector3D Q_QUICK3DUTILS_EXPORT transform(const QMatrix4x4 &m, const QVector3D &v); |
| 72 | QVector4D Q_QUICK3DUTILS_EXPORT transform(const QMatrix4x4 &m, const QVector4D &v); |
| 73 | QVector3D Q_QUICK3DUTILS_EXPORT getPosition(const QMatrix4x4 &m); |
| 74 | QVector3D Q_QUICK3DUTILS_EXPORT getScale(const QMatrix4x4 &m); |
| 75 | |
| 76 | bool Q_QUICK3DUTILS_EXPORT decompose(const QMatrix4x4 &m, QVector3D &position, QVector3D &scale, QQuaternion &rotation); |
| 77 | |
| 78 | inline void flip(QMatrix4x4 &matrix) |
| 79 | { |
| 80 | // Flip between left-handed and right-handed orientation |
| 81 | float *writePtr(matrix.data()); |
| 82 | // rotation conversion |
| 83 | writePtr[0 * 4 + 2] *= -1; |
| 84 | writePtr[1 * 4 + 2] *= -1; |
| 85 | writePtr[2 * 4 + 0] *= -1; |
| 86 | writePtr[2 * 4 + 1] *= -1; |
| 87 | // translation conversion |
| 88 | writePtr[3 * 4 + 2] *= -1; |
| 89 | } |
| 90 | |
| 91 | } |
| 92 | |
| 93 | namespace quat { |
| 94 | bool Q_QUICK3DUTILS_EXPORT isFinite(const QQuaternion &q); |
| 95 | |
| 96 | float Q_QUICK3DUTILS_EXPORT magnitude(const QQuaternion &q); |
| 97 | |
| 98 | bool Q_QUICK3DUTILS_EXPORT isSane(const QQuaternion &q); |
| 99 | |
| 100 | bool Q_QUICK3DUTILS_EXPORT isUnit(const QQuaternion &q); |
| 101 | |
| 102 | QVector3D Q_QUICK3DUTILS_EXPORT rotated(const QQuaternion &q, const QVector3D &v); |
| 103 | |
| 104 | QVector3D Q_QUICK3DUTILS_EXPORT inverseRotated(const QQuaternion &q, const QVector3D &v); |
| 105 | } |
| 106 | |
| 107 | namespace color { |
| 108 | QVector4D Q_QUICK3DUTILS_EXPORT sRGBToLinear(const QColor &color); |
| 109 | QColor Q_QUICK3DUTILS_EXPORT sRGBToLinearColor(const QColor &color); |
| 110 | } |
| 111 | |
| 112 | template<typename TDataType> |
| 113 | QSSGDataRef<TDataType> PtrAtOffset(quint8 *baseData, quint32 offset, quint32 byteSize) |
| 114 | { |
| 115 | return QSSGDataRef<TDataType>(byteSize ? reinterpret_cast<TDataType *>(baseData + offset) : nullptr, |
| 116 | byteSize / sizeof(TDataType)); |
| 117 | } |
| 118 | |
| 119 | Q_QUICK3DUTILS_EXPORT const char *nonNull(const char *src); |
| 120 | |
| 121 | inline QVector3D degToRad(const QVector3D &v) { |
| 122 | return QVector3D(qDegreesToRadians(degrees: v.x()), qDegreesToRadians(degrees: v.y()), qDegreesToRadians(degrees: v.z())); |
| 123 | } |
| 124 | |
| 125 | inline QVector3D radToDeg(const QVector3D &v) { |
| 126 | return QVector3D(qRadiansToDegrees(radians: v.x()), qRadiansToDegrees(radians: v.y()), qRadiansToDegrees(radians: v.z())); |
| 127 | } |
| 128 | |
| 129 | namespace rect { |
| 130 | // Return coordinates in pixels but relative to this rect. |
| 131 | inline QVector2D toRectRelative(const QRectF &r, const QVector2D &absoluteCoordinates) |
| 132 | { |
| 133 | return QVector2D(absoluteCoordinates.x() - float(r.x()), absoluteCoordinates.y() - float(r.y())); |
| 134 | } |
| 135 | |
| 136 | inline QVector2D halfDims(const QRectF &r) |
| 137 | { |
| 138 | return QVector2D(float(r.width() / 2.0), float(r.height() / 2.0)); |
| 139 | } |
| 140 | |
| 141 | // Take coordinates in global space and move local space where 0,0 is the center |
| 142 | // of the rect but return value in pixels, not in normalized -1,1 range |
| 143 | inline QVector2D toNormalizedRectRelative(const QRectF &r, QVector2D absoluteCoordinates) |
| 144 | { |
| 145 | // normalize them |
| 146 | const QVector2D relativeCoords(toRectRelative(r, absoluteCoordinates)); |
| 147 | const QVector2D halfD(halfDims(r)); |
| 148 | const QVector2D normalized((relativeCoords.x() / halfD.x()) - 1.0f, (relativeCoords.y() / halfD.y()) - 1.0f); |
| 149 | return QVector2D(normalized.x() * halfD.x(), normalized.y() * halfD.y()); |
| 150 | } |
| 151 | |
| 152 | inline QVector2D relativeToNormalizedCoordinates(const QRectF &r, QVector2D rectRelativeCoords) |
| 153 | { |
| 154 | return { (rectRelativeCoords.x() / halfDims(r).x()) - 1.0f, (rectRelativeCoords.y() / halfDims(r).y()) - 1.0f }; |
| 155 | } |
| 156 | |
| 157 | // Normalized coordinates are in the range of -1,1 where -1 is the left, bottom edges |
| 158 | // and 1 is the top,right edges. |
| 159 | inline QVector2D absoluteToNormalizedCoordinates(const QRectF &r, const QVector2D &absoluteCoordinates) |
| 160 | { |
| 161 | return relativeToNormalizedCoordinates(r, rectRelativeCoords: toRectRelative(r, absoluteCoordinates)); |
| 162 | } |
| 163 | |
| 164 | inline QVector2D toAbsoluteCoords(const QRectF &r, const QVector2D &inRelativeCoords) |
| 165 | { |
| 166 | return QVector2D(inRelativeCoords.x() + float(r.x()), inRelativeCoords.y() + float(r.y())); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | } // namespace QSSGUtils |
| 171 | |
| 172 | class RotationData |
| 173 | { |
| 174 | public: |
| 175 | RotationData() = default; |
| 176 | explicit RotationData(const QVector3D &r) |
| 177 | : m_quatRot() |
| 178 | , m_eulerRot(r) |
| 179 | , m_dirty(Dirty::Quaternion) |
| 180 | {} |
| 181 | explicit RotationData(const QQuaternion &r) |
| 182 | : m_quatRot(r.normalized()) |
| 183 | , m_eulerRot() |
| 184 | , m_dirty(Dirty::Euler) |
| 185 | {} |
| 186 | |
| 187 | RotationData &operator=(const QVector3D &r) noexcept |
| 188 | { |
| 189 | m_eulerRot = r; |
| 190 | m_dirty = Dirty::Quaternion; |
| 191 | return *this; |
| 192 | } |
| 193 | RotationData &operator=(const QQuaternion &r) noexcept |
| 194 | { |
| 195 | m_quatRot = r.normalized(); |
| 196 | m_dirty = Dirty::Euler; |
| 197 | return *this; |
| 198 | } |
| 199 | |
| 200 | friend inline bool operator ==(const RotationData &a, const RotationData &b) { |
| 201 | if (a.m_dirty == Dirty::None && b.m_dirty == Dirty::None) |
| 202 | return fuzzyQuaternionCompare(a: a.m_quatRot, b: b.m_quatRot); |
| 203 | |
| 204 | return fuzzyQuaternionCompare(a: QQuaternion(a), b: QQuaternion(b)); |
| 205 | } |
| 206 | |
| 207 | friend inline bool operator !=(const RotationData &a, const RotationData &b) { return !(a == b); } |
| 208 | |
| 209 | friend inline bool operator ==(const RotationData &a, const QVector3D &eulerRotation) |
| 210 | { |
| 211 | if (a.m_dirty == Dirty::None) |
| 212 | return qFuzzyCompare(v1: a.m_eulerRot, v2: eulerRotation); |
| 213 | |
| 214 | return qFuzzyCompare(v1: QVector3D(a), v2: eulerRotation); |
| 215 | } |
| 216 | friend inline bool operator !=(const RotationData &a, const QVector3D &eulerRotation) { return !(a == eulerRotation); } |
| 217 | |
| 218 | friend inline bool operator ==(const RotationData &a, const QQuaternion &rotation) |
| 219 | { |
| 220 | if (a.m_dirty == Dirty::None) |
| 221 | return fuzzyQuaternionCompare(a: a.m_quatRot, b: rotation); |
| 222 | |
| 223 | return fuzzyQuaternionCompare(a: QQuaternion(a), b: rotation); |
| 224 | } |
| 225 | friend inline bool operator !=(const RotationData &a, const QQuaternion &rotation) { return !(a == rotation); } |
| 226 | |
| 227 | [[nodiscard]] inline QVector3D getEulerRotation() const |
| 228 | { |
| 229 | if (m_dirty == Dirty::Euler) { |
| 230 | m_eulerRot = m_quatRot.toEulerAngles(); |
| 231 | m_dirty = Dirty::None; |
| 232 | } |
| 233 | |
| 234 | return m_eulerRot; |
| 235 | } |
| 236 | |
| 237 | [[nodiscard]] inline QQuaternion getQuaternionRotation() const |
| 238 | { |
| 239 | if (m_dirty == Dirty::Quaternion) { |
| 240 | m_quatRot = QQuaternion::fromEulerAngles(eulerAngles: m_eulerRot).normalized(); |
| 241 | m_dirty = Dirty::None; |
| 242 | } |
| 243 | |
| 244 | return m_quatRot; |
| 245 | } |
| 246 | |
| 247 | [[nodiscard]] inline QMatrix3x3 toRotationMatrix() const { return getQuaternionRotation().toRotationMatrix(); } |
| 248 | |
| 249 | [[nodiscard]] inline operator QQuaternion() const { return getQuaternionRotation(); } |
| 250 | [[nodiscard]] inline operator QVector3D() const { return getEulerRotation(); } |
| 251 | |
| 252 | private: |
| 253 | friend class ::tst_RotationDataClass; |
| 254 | |
| 255 | static constexpr double dotProduct(const QQuaternion &q1, const QQuaternion &q2) noexcept |
| 256 | { |
| 257 | return double(q1.scalar()) * double(q2.scalar()) |
| 258 | + double(q1.x()) * double(q2.x()) |
| 259 | + double(q1.y()) * double(q2.y()) |
| 260 | + double(q1.z()) * double(q2.z()); |
| 261 | } |
| 262 | |
| 263 | [[nodiscard]] static constexpr bool fuzzyQuaternionCompare(const QQuaternion &a, const QQuaternion &b) |
| 264 | { |
| 265 | return qFuzzyCompare(p1: qAbs(t: dotProduct(q1: a, q2: b)), p2: 1.0); |
| 266 | } |
| 267 | |
| 268 | enum class Dirty |
| 269 | { |
| 270 | None, |
| 271 | Quaternion = 0x1, |
| 272 | Euler = 0x2 |
| 273 | }; |
| 274 | |
| 275 | mutable QQuaternion m_quatRot; // Should always be normalized |
| 276 | mutable QVector3D m_eulerRot; |
| 277 | mutable Dirty m_dirty { Dirty::None }; |
| 278 | }; |
| 279 | |
| 280 | namespace DebugViewHelpers { |
| 281 | template<typename T> |
| 282 | inline void ensureDebugObjectName(T *node, QObject *src) |
| 283 | { |
| 284 | if (!node->debugObjectName.isEmpty()) |
| 285 | return; |
| 286 | node->debugObjectName = src->objectName(); |
| 287 | if (node->debugObjectName.isEmpty()) |
| 288 | node->debugObjectName = QString::fromLatin1(ba: src->metaObject()->className()); |
| 289 | if (node->debugObjectName.isEmpty()) |
| 290 | node->debugObjectName = QString::asprintf(format: "%p" , src); |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | QT_END_NAMESPACE |
| 295 | |
| 296 | #endif // QSSGUTILS_H |
| 297 | |