| 1 | // Copyright (C) 2024 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
| 3 | |
| 4 | #include "torusgeometry_p.h" |
| 5 | #include <limits> |
| 6 | |
| 7 | #if QT_CONFIG(concurrent) |
| 8 | #include <QtConcurrentRun> |
| 9 | #endif |
| 10 | |
| 11 | QT_BEGIN_NAMESPACE |
| 12 | |
| 13 | /*! |
| 14 | \qmltype TorusGeometry |
| 15 | \inqmlmodule QtQuick3D.Helpers |
| 16 | \inherits Geometry |
| 17 | \since 6.9 |
| 18 | \brief Provides geometry for a torus. |
| 19 | |
| 20 | TorusGeometry is a geometry type that represents a torus. The torus is defined by the number of |
| 21 | rings and segments, and the radius of the torus. |
| 22 | |
| 23 | */ |
| 24 | |
| 25 | /*! |
| 26 | \qmlproperty int TorusGeometry::rings |
| 27 | Specifies the number of rings in the torus. The default value is 50. |
| 28 | */ |
| 29 | |
| 30 | /*! |
| 31 | \qmlproperty int TorusGeometry::segments |
| 32 | Specifies the number of segments in the torus. The default value is 50. |
| 33 | */ |
| 34 | |
| 35 | /*! |
| 36 | \qmlproperty real TorusGeometry::radius |
| 37 | Specifies the radius of the torus. The default value is 100. |
| 38 | */ |
| 39 | |
| 40 | /*! |
| 41 | \qmlproperty real TorusGeometry::tubeRadius |
| 42 | Specifies the radius of the tube of the torus. The default value is 10. |
| 43 | */ |
| 44 | |
| 45 | /*! |
| 46 | \qmlproperty bool TorusGeometry::asynchronous |
| 47 | |
| 48 | This property holds whether the geometry generation should be asynchronous. |
| 49 | */ |
| 50 | |
| 51 | /*! |
| 52 | \qmlproperty bool TorusGeometry::status |
| 53 | \readonly |
| 54 | |
| 55 | This property holds the status of the geometry generation when asynchronous is true. |
| 56 | |
| 57 | \value TorusGeometry.Null The geometry generation has not started |
| 58 | \value TorusGeometry.Ready The geometry generation is complete. |
| 59 | \value TorusGeometry.Loading The geometry generation is in progress. |
| 60 | \value TorusGeometry.Error The geometry generation failed. |
| 61 | */ |
| 62 | |
| 63 | TorusGeometry::TorusGeometry(QQuick3DObject *parent) |
| 64 | : QQuick3DGeometry(parent) |
| 65 | { |
| 66 | #if QT_CONFIG(concurrent) |
| 67 | connect(sender: &m_geometryDataWatcher, signal: &QFutureWatcher<GeometryData>::finished, context: this, slot: &TorusGeometry::requestFinished); |
| 68 | #endif |
| 69 | scheduleGeometryUpdate(); |
| 70 | } |
| 71 | |
| 72 | TorusGeometry::~TorusGeometry() |
| 73 | { |
| 74 | |
| 75 | } |
| 76 | |
| 77 | int TorusGeometry::rings() const |
| 78 | { |
| 79 | return m_rings; |
| 80 | } |
| 81 | |
| 82 | void TorusGeometry::setRings(int newRings) |
| 83 | { |
| 84 | if (m_rings == newRings) |
| 85 | return; |
| 86 | m_rings = newRings; |
| 87 | emit ringsChanged(); |
| 88 | scheduleGeometryUpdate(); |
| 89 | } |
| 90 | |
| 91 | int TorusGeometry::segments() const |
| 92 | { |
| 93 | return m_segments; |
| 94 | } |
| 95 | |
| 96 | void TorusGeometry::setSegments(int newSegments) |
| 97 | { |
| 98 | if (m_segments == newSegments) |
| 99 | return; |
| 100 | m_segments = newSegments; |
| 101 | emit segmentsChanged(); |
| 102 | scheduleGeometryUpdate(); |
| 103 | } |
| 104 | |
| 105 | float TorusGeometry::radius() const |
| 106 | { |
| 107 | return m_radius; |
| 108 | } |
| 109 | |
| 110 | void TorusGeometry::setRadius(float newRadius) |
| 111 | { |
| 112 | if (qFuzzyCompare(p1: m_radius, p2: newRadius)) |
| 113 | return; |
| 114 | m_radius = newRadius; |
| 115 | emit radiusChanged(); |
| 116 | scheduleGeometryUpdate(); |
| 117 | } |
| 118 | |
| 119 | float TorusGeometry::tubeRadius() const |
| 120 | { |
| 121 | return m_tubeRadius; |
| 122 | } |
| 123 | |
| 124 | void TorusGeometry::setTubeRadius(float newTubeRadius) |
| 125 | { |
| 126 | if (qFuzzyCompare(p1: m_tubeRadius, p2: newTubeRadius)) |
| 127 | return; |
| 128 | m_tubeRadius = newTubeRadius; |
| 129 | emit tubeRadiusChanged(); |
| 130 | scheduleGeometryUpdate(); |
| 131 | } |
| 132 | |
| 133 | bool TorusGeometry::asynchronous() const |
| 134 | { |
| 135 | return m_asynchronous; |
| 136 | } |
| 137 | |
| 138 | void TorusGeometry::setAsynchronous(bool newAsynchronous) |
| 139 | { |
| 140 | if (m_asynchronous == newAsynchronous) |
| 141 | return; |
| 142 | m_asynchronous = newAsynchronous; |
| 143 | emit asynchronousChanged(); |
| 144 | } |
| 145 | |
| 146 | TorusGeometry::Status TorusGeometry::status() const |
| 147 | { |
| 148 | return m_status; |
| 149 | } |
| 150 | |
| 151 | void TorusGeometry::doUpdateGeometry() |
| 152 | { |
| 153 | // reset the flag since we are processing the update |
| 154 | m_geometryUpdateRequested = false; |
| 155 | |
| 156 | #if QT_CONFIG(concurrent) |
| 157 | if (m_geometryDataFuture.isRunning()) { |
| 158 | m_pendingAsyncUpdate = true; |
| 159 | return; |
| 160 | } |
| 161 | #endif |
| 162 | |
| 163 | // Check for validity of the parameters before generation |
| 164 | if (m_rings <= 0 || m_segments <= 0 || m_radius <= 0 || m_tubeRadius <= 0) { |
| 165 | clear(); |
| 166 | update(); |
| 167 | return; |
| 168 | } |
| 169 | |
| 170 | #if QT_CONFIG(concurrent) |
| 171 | |
| 172 | if (m_asynchronous) { |
| 173 | m_geometryDataFuture = QtConcurrent::run(f&: generateTorusGeometryAsync, |
| 174 | args&: m_rings, |
| 175 | args&: m_segments, |
| 176 | args&: m_radius, |
| 177 | args&: m_tubeRadius); |
| 178 | m_geometryDataWatcher.setFuture(m_geometryDataFuture); |
| 179 | m_status = Status::Loading; |
| 180 | Q_EMIT statusChanged(); |
| 181 | } else { |
| 182 | #else |
| 183 | { |
| 184 | |
| 185 | #endif // QT_CONFIG(concurrent) |
| 186 | updateGeometry(geometryData: generateTorusGeometry(rings: m_rings, segments: m_segments, radius: m_radius, tubeRadius: m_tubeRadius)); |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | void TorusGeometry::requestFinished() |
| 191 | { |
| 192 | #if QT_CONFIG(concurrent) |
| 193 | const auto output = m_geometryDataFuture.takeResult(); |
| 194 | updateGeometry(geometryData: output); |
| 195 | #endif |
| 196 | } |
| 197 | |
| 198 | void TorusGeometry::scheduleGeometryUpdate() |
| 199 | { |
| 200 | if (!m_geometryUpdateRequested) { |
| 201 | QMetaObject::invokeMethod(obj: this, member: "doUpdateGeometry" , c: Qt::QueuedConnection); |
| 202 | m_geometryUpdateRequested = true; |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | void TorusGeometry::updateGeometry(const GeometryData &geometryData) |
| 207 | { |
| 208 | setStride(sizeof(float) * 8); // 3 for position, 3 for normal, 2 for uv0 |
| 209 | setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles); |
| 210 | addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic, |
| 211 | offset: 0, |
| 212 | componentType: QQuick3DGeometry::Attribute::F32Type); |
| 213 | addAttribute(semantic: QQuick3DGeometry::Attribute::NormalSemantic, |
| 214 | offset: 3 * sizeof(float), |
| 215 | componentType: QQuick3DGeometry::Attribute::F32Type); |
| 216 | addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic, |
| 217 | offset: 6 * sizeof(float), |
| 218 | componentType: QQuick3DGeometry::Attribute::F32Type); |
| 219 | addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic, |
| 220 | offset: 0, |
| 221 | componentType: QQuick3DGeometry::Attribute::U32Type); |
| 222 | |
| 223 | setBounds(min: geometryData.boundsMin, max: geometryData.boundsMax); |
| 224 | setVertexData(geometryData.vertexData); |
| 225 | setIndexData(geometryData.indexData); |
| 226 | |
| 227 | // If the geometry update was requested while the geometry was being generated asynchronously, |
| 228 | // we need to schedule another geometry update now that the geometry is ready. |
| 229 | if (m_pendingAsyncUpdate) { |
| 230 | m_pendingAsyncUpdate = false; |
| 231 | scheduleGeometryUpdate(); |
| 232 | } else { |
| 233 | m_status = Status::Ready; |
| 234 | Q_EMIT statusChanged(); |
| 235 | } |
| 236 | update(); |
| 237 | } |
| 238 | |
| 239 | const size_t FLOAT_SIZE = sizeof(float); |
| 240 | const size_t VEC3_SIZE = sizeof(QVector3D); |
| 241 | const size_t VEC2_SIZE = sizeof(QVector2D); |
| 242 | const size_t UINT_SIZE = sizeof(quint32); |
| 243 | |
| 244 | TorusGeometry::GeometryData TorusGeometry::generateTorusGeometry(int rings, int segments, float radius, float tubeRadius) |
| 245 | { |
| 246 | GeometryData geomData; |
| 247 | |
| 248 | // Bounds initialization |
| 249 | QVector3D boundsMin(std::numeric_limits<float>::max(), |
| 250 | std::numeric_limits<float>::max(), |
| 251 | std::numeric_limits<float>::max()); |
| 252 | |
| 253 | QVector3D boundsMax(std::numeric_limits<float>::lowest(), |
| 254 | std::numeric_limits<float>::lowest(), |
| 255 | std::numeric_limits<float>::lowest()); |
| 256 | |
| 257 | // Pre-calculate the number of vertices and indices |
| 258 | int numVerts = (rings + 1) * (segments + 1); |
| 259 | int numIndices = rings * segments * 6; // Two triangles per quad |
| 260 | |
| 261 | // Reserve space in the QByteArrays (vertex data: position (3 floats), normal (3 floats), UV (2 floats)) |
| 262 | int vertexDataSize = numVerts * (VEC3_SIZE + VEC3_SIZE + VEC2_SIZE); // Position, Normal, UV |
| 263 | geomData.vertexData.resize(size: vertexDataSize); |
| 264 | |
| 265 | // Indices are stored as unsigned 32-bit integers |
| 266 | int indexDataSize = numIndices * UINT_SIZE; |
| 267 | geomData.indexData.resize(size: indexDataSize); |
| 268 | |
| 269 | // Get raw pointers to the QByteArray data |
| 270 | char* vertexPtr = geomData.vertexData.data(); |
| 271 | char* indexPtr = geomData.indexData.data(); |
| 272 | |
| 273 | int vertexOffset = 0; |
| 274 | int indexOffset = 0; |
| 275 | |
| 276 | // Iterate over the rings and segments to compute vertices, normals, and UVs |
| 277 | for (int i = 0; i <= rings; ++i) { |
| 278 | for (int j = 0; j <= segments; ++j) { |
| 279 | float u = (i / (float)rings) * M_PI * 2; |
| 280 | float v = (j / (float)segments) * M_PI * 2; |
| 281 | |
| 282 | float centerX = radius * qCos(v: u); |
| 283 | float centerZ = radius * qSin(v: u); |
| 284 | |
| 285 | float posX = centerX + tubeRadius * qCos(v) * qCos(v: u); |
| 286 | float posY = tubeRadius * qSin(v); |
| 287 | float posZ = centerZ + tubeRadius * qCos(v) * qSin(v: u); |
| 288 | |
| 289 | // Update bounds |
| 290 | boundsMin.setX(qMin(a: boundsMin.x(), b: posX)); |
| 291 | boundsMin.setY(qMin(a: boundsMin.y(), b: posY)); |
| 292 | boundsMin.setZ(qMin(a: boundsMin.z(), b: posZ)); |
| 293 | |
| 294 | boundsMax.setX(qMax(a: boundsMax.x(), b: posX)); |
| 295 | boundsMax.setY(qMax(a: boundsMax.y(), b: posY)); |
| 296 | boundsMax.setZ(qMax(a: boundsMax.z(), b: posZ)); |
| 297 | |
| 298 | // Position data (3 floats) |
| 299 | memcpy(dest: vertexPtr + vertexOffset, src: &posX, n: FLOAT_SIZE); |
| 300 | memcpy(dest: vertexPtr + vertexOffset + FLOAT_SIZE, src: &posY, n: FLOAT_SIZE); |
| 301 | memcpy(dest: vertexPtr + vertexOffset + 2 * FLOAT_SIZE, src: &posZ, n: FLOAT_SIZE); |
| 302 | vertexOffset += 3 * FLOAT_SIZE; |
| 303 | |
| 304 | // Calculate normal |
| 305 | QVector3D normal = QVector3D(posX - centerX, posY, posZ - centerZ).normalized(); |
| 306 | |
| 307 | // Normal data (3 floats) |
| 308 | memcpy(dest: vertexPtr + vertexOffset, src: &normal, n: VEC3_SIZE); |
| 309 | vertexOffset += VEC3_SIZE; |
| 310 | |
| 311 | // UV data (2 floats) |
| 312 | float uvX = 1.0f - (i / (float)rings); |
| 313 | float uvY = j / (float)segments; |
| 314 | memcpy(dest: vertexPtr + vertexOffset, src: &uvX, n: FLOAT_SIZE); |
| 315 | memcpy(dest: vertexPtr + vertexOffset + FLOAT_SIZE, src: &uvY, n: FLOAT_SIZE); |
| 316 | vertexOffset += 2 * FLOAT_SIZE; |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | // Generate indices |
| 321 | for (int i = 0; i < rings; ++i) { |
| 322 | for (int j = 0; j < segments; ++j) { |
| 323 | int a = (segments + 1) * i + j; |
| 324 | int b = (segments + 1) * (i + 1) + j; |
| 325 | int c = (segments + 1) * (i + 1) + j + 1; |
| 326 | int d = (segments + 1) * i + j + 1; |
| 327 | |
| 328 | // First triangle (a, d, b) |
| 329 | memcpy(dest: indexPtr + indexOffset, src: &a, n: UINT_SIZE); |
| 330 | memcpy(dest: indexPtr + indexOffset + UINT_SIZE, src: &d, n: UINT_SIZE); |
| 331 | memcpy(dest: indexPtr + indexOffset + 2 * UINT_SIZE, src: &b, n: UINT_SIZE); |
| 332 | indexOffset += 3 * UINT_SIZE; |
| 333 | |
| 334 | // Second triangle (b, d, c) |
| 335 | memcpy(dest: indexPtr + indexOffset, src: &b, n: UINT_SIZE); |
| 336 | memcpy(dest: indexPtr + indexOffset + UINT_SIZE, src: &d, n: UINT_SIZE); |
| 337 | memcpy(dest: indexPtr + indexOffset + 2 * UINT_SIZE, src: &c, n: UINT_SIZE); |
| 338 | indexOffset += 3 * UINT_SIZE; |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | // Set the computed bounds |
| 343 | geomData.boundsMin = boundsMin; |
| 344 | geomData.boundsMax = boundsMax; |
| 345 | |
| 346 | return geomData; |
| 347 | } |
| 348 | |
| 349 | #if QT_CONFIG(concurrent) |
| 350 | void TorusGeometry::generateTorusGeometryAsync(QPromise<TorusGeometry::GeometryData> &promise, int rings, int segments, float radius, float tubeRadius) |
| 351 | { |
| 352 | auto output = generateTorusGeometry(rings, segments, radius, tubeRadius); |
| 353 | promise.addResult(result&: output); |
| 354 | } |
| 355 | #endif |
| 356 | |
| 357 | QT_END_NAMESPACE |
| 358 | |