Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
proceduralmesh.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7
8#include <QtQuick3D/private/qquick3dobject_p.h>
9
10#include <QtQuick/QQuickWindow>
11
12#include <rhi/qrhi.h>
13
15
16/*!
17 \qmltype ProceduralMesh
18 \inqmlmodule QtQuick3D.Helpers
19 \inherits Geometry
20 \brief Allows creation of Geometry from QML.
21 \since 6.6
22
23 ProceduralMesh is a helper type that allows creation of Geometry instances
24 from QML. The Geometry component is Abstract, and is usually created
25 from C++.
26
27 \qml
28 component TorusMesh : ProceduralMesh {
29 property real rings: 50
30 property real segments: 50
31 property real radius: 100.0
32 property real tubeRadius: 10.0
33 property var meshArrays: generateTorus(rings, segments, radius, tubeRadius)
34 positions: meshArrays.verts
35 normals: meshArrays.normals
36 uv0s: meshArrays.uvs
37 indexes: meshArrays.indices
38
39 function generateTorus(rings: real, segments: real, radius: real, tubeRadius: real) {
40 let verts = []
41 let normals = []
42 let uvs = []
43 let indices = []
44
45 for (let i = 0; i <= rings; ++i) {
46 for (let j = 0; j <= segments; ++j) {
47 let u = i / rings * Math.PI * 2;
48 let v = j / segments * Math.PI * 2;
49
50 let centerX = radius * Math.cos(u);
51 let centerZ = radius * Math.sin(u);
52
53 let posX = centerX + tubeRadius * Math.cos(v) * Math.cos(u);
54 let posY = tubeRadius * Math.sin(v);
55 let posZ = centerZ + tubeRadius * Math.cos(v) * Math.sin(u);
56
57 verts.push(Qt.vector3d(posX, posY, posZ));
58
59 let normal = Qt.vector3d(posX - centerX, posY, posZ - centerZ).normalized();
60 normals.push(normal);
61
62 uvs.push(Qt.vector2d(i / rings, j / segments));
63 }
64 }
65
66 for (let i = 0; i < rings; ++i) {
67 for (let j = 0; j < segments; ++j) {
68 let a = (segments + 1) * i + j;
69 let b = (segments + 1) * (i + 1) + j;
70 let c = (segments + 1) * (i + 1) + j + 1;
71 let d = (segments + 1) * i + j + 1;
72
73 // Generate two triangles for each quad in the mesh
74 // Adjust order to be counter-clockwise
75 indices.push(a, d, b);
76 indices.push(b, d, c);
77 }
78 }
79 return { verts: verts, normals: normals, uvs: uvs, indices: indices }
80 }
81 }
82 \endqml
83
84 The above code defines a component TorusMesh that can be used as Geometry for use
85 with a Model component. When the ring, segments, radius or tubeRadius properties
86 are modified the geometry will be updated.
87
88 The ProceduralMesh component is not as flexible nor as performant as creating
89 Geometry in C++, but makes up for it in convenience and simplicity. The
90 properties are fixed attribute lists that when filled will automatically
91 generate the necessary buffers.
92
93*/
94
95/*!
96 \qmlproperty List<QVector3D> ProceduralMesh::positions
97 The positions attribute list. If this list remains empty nothing no geometry
98 will be generated.
99*/
100
101/*!
102 \qmlproperty List<QVector3D> ProceduralMesh::normals
103 Holds the normals attribute list.
104*/
105
106/*!
107 \qmlproperty List<QVector3D> ProceduralMesh::tangents
108 Holds the tangents attribute list.
109*/
110
111/*!
112 \qmlproperty List<QVector3D> ProceduralMesh::binormals
113 Holds the binormals attribute list.
114*/
115
116/*!
117 \qmlproperty List<QVector2D> ProceduralMesh::uv0s
118 This property defines a list of uv coordinates for the first uv channel (uv0)
119*/
120
121/*!
122 \qmlproperty List<QVector2D> ProceduralMesh::uv1s
123 This property defines a list of uv coordinates for the second uv channel (uv1)
124*/
125
126/*!
127 \qmlproperty List<QVector4D> ProceduralMesh::colors
128 This property defines a list of vertex color values.
129*/
130
131/*!
132 \qmlproperty List<QVector4D> ProceduralMesh::joints
133 This property defines a list of joint indices for skinning.
134*/
135
136/*!
137 \qmlproperty List<QVector4D> ProceduralMesh::weights
138 This property defines a list of joint weights for skinning.
139*/
140
141/*!
142 \qmlproperty List<int> ProceduralMesh::indexes
143 This property defines a list of indexes into the attribute lists. If this list remains empty
144 the vertex buffer values will be used directly.
145*/
146
147/*!
148 \qmlproperty enumeration ProceduralMesh::primitiveMode
149
150 This property defines the primitive mode to use when rendering the geometry.
151
152 \value ProceduralMesh.Points The points primitive mode is used.
153 \value ProceduralMesh.LineStrip The line strip primitive mode is used.
154 \value ProceduralMesh.Lines The lines primitive mode is used.
155 \value ProceduralMesh.TriangleStrip The triangles strip primitive mode is
156 used.
157 \value ProceduralMesh.TriangleFan The triangle fan primitive mode is used.
158 \value ProceduralMesh.Triangles The triangles primitive mode is used.
159 \default ProceduralMesh.Triangles
160
161 \note Not all modes are supported on all rendering backends.
162*/
163
164/*!
165 \qmlproperty List<ProceduralMeshSubset> ProceduralMesh::subsets
166
167 This property defines a list of subsets to split the geometry data into.
168 Each subset can have it's own material. The order of this array
169 corresponds to the materials list of Model when using this geometry.
170
171 This property is optional and when empty results in a single subset.
172
173 \note Any subset that specifies values outside of the range of available
174 vertex/index values will lead to that subset being ignored.
175*/
176
177/*!
178 \qmltype ProceduralMeshSubset
179 \inqmlmodule QtQuick3D.Helpers
180 \inherits QtObject
181 \brief Defines a subset of a ProceduralMesh.
182 \since 6.6
183
184 This type defines a subset of a ProceduralMesh. Each subset can have it's own
185 material and can be used to split the geometry into multiple draw calls.
186
187 \sa ProceduralMesh::subsets
188
189*/
190
191/*!
192 \qmlproperty int ProceduralMeshSubset::offset
193 This property defines the starting index for this subset. \default 0
194*/
195
196/*!
197 \qmlproperty int ProceduralMeshSubset::count
198 This property defines the number of indices to use for this subset. This property must be set for the subset to have content.
199
200 \default 0
201*/
202
203/*!
204 \qmlproperty Material ProceduralMeshSubset::name
205 This property defines a name of the subset. This property is optional, and is only used to
206 tag the subset for debugging purposes.
207*/
208
209ProceduralMesh::ProceduralMesh()
210{
211
212}
213
214QList<QVector3D> ProceduralMesh::positions() const
215{
216 return m_positions;
217}
218
219void ProceduralMesh::setPositions(const QList<QVector3D> &newPositions)
220{
221 if (m_positions == newPositions)
222 return;
223 m_positions = newPositions;
224 Q_EMIT positionsChanged();
225 requestUpdate();
226}
227
228ProceduralMesh::PrimitiveMode ProceduralMesh::primitiveMode() const
229{
230 return m_primitiveMode;
231}
232
233void ProceduralMesh::setPrimitiveMode(PrimitiveMode newPrimitiveMode)
234{
235 if (m_primitiveMode == newPrimitiveMode)
236 return;
237
238 // Do some sanity checking
239 if (newPrimitiveMode < Points || newPrimitiveMode > Triangles) {
240 qWarning() << "Invalid primitive mode specified";
241 return;
242 }
243
244 if (newPrimitiveMode == PrimitiveMode::TriangleFan) {
245 if (!supportsTriangleFanPrimitive()) {
246 qWarning() << "TriangleFan is not supported by the current backend";
247 return;
248 }
249 }
250
251 m_primitiveMode = newPrimitiveMode;
252 Q_EMIT primitiveModeChanged();
253 requestUpdate();
254}
255
256void ProceduralMesh::requestUpdate()
257{
258 if (!m_updateRequested) {
259 QMetaObject::invokeMethod(this, "updateGeometry", Qt::QueuedConnection);
260 m_updateRequested = true;
261 }
262}
263
264void ProceduralMesh::updateGeometry()
265{
266 m_updateRequested = false;
267 // reset the geometry
268 clear();
269
270 setPrimitiveType(PrimitiveType(m_primitiveMode));
271
272 // Figure out which attributes are being used
273 const auto expectedLength = m_positions.size();
274 bool hasPositions = !m_positions.isEmpty();
275 if (!hasPositions) {
276 setStride(0);
277 update();
278 return; // If there are no positions, there is no point :-)
279 }
280 bool hasNormals = m_normals.size() >= expectedLength;
281 bool hasTangents = m_tangents.size() >= expectedLength;
282 bool hasBinormals = m_binormals.size() >= expectedLength;
283 bool hasUV0s = m_uv0s.size() >= expectedLength;
284 bool hasUV1s = m_uv1s.size() >= expectedLength;
285 bool hasColors = m_colors.size() >= expectedLength;
286 bool hasJoints = m_joints.size() >= expectedLength;
287 bool hasWeights = m_weights.size() >= expectedLength;
288 bool hasIndexes = !m_indexes.isEmpty();
289
290 int offset = 0;
291 if (hasPositions) {
292 addAttribute(Attribute::Semantic::PositionSemantic, offset, Attribute::ComponentType::F32Type);
293 offset += 3 * sizeof(float);
294 }
295
296 if (hasNormals) {
297 addAttribute(Attribute::Semantic::NormalSemantic, offset, Attribute::ComponentType::F32Type);
298 offset += 3 * sizeof(float);
299 }
300
301 if (hasTangents) {
302 addAttribute(Attribute::Semantic::TangentSemantic, offset, Attribute::ComponentType::F32Type);
303 offset += 3 * sizeof(float);
304 }
305
306 if (hasBinormals) {
307 addAttribute(Attribute::Semantic::BinormalSemantic, offset, Attribute::ComponentType::F32Type);
308 offset += 3 * sizeof(float);
309 }
310
311 if (hasUV0s) {
312 addAttribute(Attribute::Semantic::TexCoord0Semantic, offset, Attribute::ComponentType::F32Type);
313 offset += 2 * sizeof(float);
314 }
315
316 if (hasUV1s) {
317 addAttribute(Attribute::Semantic::TexCoord1Semantic, offset, Attribute::ComponentType::F32Type);
318 offset += 2 * sizeof(float);
319 }
320
321 if (hasColors) {
322 addAttribute(Attribute::Semantic::ColorSemantic, offset, Attribute::ComponentType::F32Type);
323 offset += 4 * sizeof(float);
324 }
325
326 if (hasJoints) {
327 addAttribute(Attribute::Semantic::JointSemantic, offset, Attribute::ComponentType::F32Type);
328 offset += 4 * sizeof(float);
329 }
330
331 if (hasWeights) {
332 addAttribute(Attribute::Semantic::WeightSemantic, offset, Attribute::ComponentType::F32Type);
333 offset += 4 * sizeof(float);
334 }
335
336 if (hasIndexes)
337 addAttribute(Attribute::Semantic::IndexSemantic, 0, Attribute::ComponentType::U32Type);
338
339 // Set up the vertex buffer
340 const int stride = offset;
341 const qsizetype bufferSize = expectedLength * stride;
342 setStride(stride);
343
344 QVector<float> vertexBufferData;
345 vertexBufferData.reserve(bufferSize / sizeof(float));
346
347 QVector3D minBounds;
348 QVector3D maxBounds;
349
350 for (qsizetype i = 0; i < expectedLength; ++i) {
351 // start writing float values to vertexBuffer
352 if (hasPositions) {
353 const auto &position = m_positions[i];
354 vertexBufferData.append(position.x());
355 vertexBufferData.append(position.y());
356 vertexBufferData.append(position.z());
357 minBounds.setX(qMin(minBounds.x(), position.x()));
358 maxBounds.setX(qMax(maxBounds.x(), position.x()));
359 minBounds.setY(qMin(minBounds.y(), position.y()));
360 maxBounds.setY(qMax(maxBounds.y(), position.y()));
361 minBounds.setZ(qMin(minBounds.z(), position.z()));
362 maxBounds.setZ(qMax(maxBounds.z(), position.z()));
363 }
364 if (hasNormals) {
365 const auto &normal = m_normals[i];
366 vertexBufferData.append(normal.x());
367 vertexBufferData.append(normal.y());
368 vertexBufferData.append(normal.z());
369 }
370
371 if (hasBinormals) {
372 const auto &binormal = m_binormals[i];
373 vertexBufferData.append(binormal.x());
374 vertexBufferData.append(binormal.y());
375 vertexBufferData.append(binormal.z());
376 }
377
378 if (hasTangents) {
379 const auto &tangent = m_tangents[i];
380 vertexBufferData.append(tangent.x());
381 vertexBufferData.append(tangent.y());
382 vertexBufferData.append(tangent.z());
383 }
384
385 if (hasUV0s) {
386 const auto &uv0 = m_uv0s[i];
387 vertexBufferData.append(uv0.x());
388 vertexBufferData.append(uv0.y());
389 }
390
391 if (hasUV1s) {
392 const auto &uv1 = m_uv1s[i];
393 vertexBufferData.append(uv1.x());
394 vertexBufferData.append(uv1.y());
395 }
396
397 if (hasColors) {
398 const auto &color = m_colors[i];
399 vertexBufferData.append(color.x());
400 vertexBufferData.append(color.y());
401 vertexBufferData.append(color.z());
402 vertexBufferData.append(color.w());
403 }
404
405 if (hasJoints) {
406 const auto &joint = m_joints[i];
407 vertexBufferData.append(joint.x());
408 vertexBufferData.append(joint.y());
409 vertexBufferData.append(joint.z());
410 vertexBufferData.append(joint.w());
411 }
412
413 if (hasWeights) {
414 const auto &weight = m_weights[i];
415 vertexBufferData.append(weight.x());
416 vertexBufferData.append(weight.y());
417 vertexBufferData.append(weight.z());
418 vertexBufferData.append(weight.w());
419 }
420 }
421
422 setBounds(minBounds, maxBounds);
423 QByteArray vertexBuffer(reinterpret_cast<char *>(vertexBufferData.data()), bufferSize);
424 setVertexData(vertexBuffer);
425
426 // Index Buffer
427 if (hasIndexes) {
428 const qsizetype indexLength = m_indexes.size();
429 QByteArray indexBuffer;
430 indexBuffer.reserve(indexLength * sizeof(unsigned int));
431 for (qsizetype i = 0; i < indexLength; ++i) {
432 const auto &index = m_indexes[i];
433 indexBuffer.append(reinterpret_cast<const char *>(&index), sizeof(unsigned int));
434 }
435 setIndexData(indexBuffer);
436 }
437
438 // Subsets
439 // Subsets are optional so if none are specified the whole mesh is a single submesh
440 if (!m_subsets.isEmpty()) {
441 for (const auto &subset : m_subsets) {
442 QVector3D subsetMinBounds;
443 QVector3D subsetMaxBounds;
444 // Range checking is necessary because the user could have specified subset values
445 // that are out of range of the vertex/index buffer
446 bool outOfRange = false;
447 for (qsizetype i = subset->offset(); i < subset->offset() + subset->count(); ++i) {
448 if (hasPositions) {
449 qsizetype index = i;
450 if (hasIndexes) {
451 if (i < m_indexes.size()) {
452 index = m_indexes[i];
453 } else {
454 outOfRange = true;
455 break;
456 }
457 }
458 if (index < m_positions.size()) {
459 const auto &position = m_positions[index];
460 subsetMinBounds.setX(qMin(subsetMinBounds.x(), position.x()));
461 subsetMaxBounds.setX(qMax(subsetMaxBounds.x(), position.x()));
462 subsetMinBounds.setY(qMin(subsetMinBounds.y(), position.y()));
463 subsetMaxBounds.setY(qMax(subsetMaxBounds.y(), position.y()));
464 subsetMinBounds.setZ(qMin(subsetMinBounds.z(), position.z()));
465 subsetMaxBounds.setZ(qMax(subsetMaxBounds.z(), position.z()));
466 } else {
467 outOfRange = true;
468 break;
469 }
470 }
471 }
472 if (!outOfRange)
473 addSubset(subset->offset(), subset->count(), subsetMinBounds, subsetMaxBounds, subset->name());
474 else
475 qWarning("Skipping invalid subset: Out of Range");
476 }
477 }
478
479 update();
480 emit geometryChanged();
481}
482
483void ProceduralMesh::subsetDestroyed(QObject *subset)
484{
485 if (m_subsets.removeAll(subset))
486 requestUpdate();
487}
488
489bool ProceduralMesh::supportsTriangleFanPrimitive() const
490{
491 static bool supportQueried = false;
492 static bool triangleFanSupported = false;
493 if (!supportQueried) {
494 const auto &manager = QQuick3DObjectPrivate::get(this)->sceneManager;
495 if (manager) {
496 auto window = manager->window();
497 if (window) {
498 auto rhi = window->rhi();
499 if (rhi) {
500 triangleFanSupported = rhi->isFeatureSupported(QRhi::TriangleFanTopology);
501 supportQueried = true;
502 }
503 }
504 }
505 }
506
507 return triangleFanSupported;
508}
509
510void ProceduralMesh::qmlAppendProceduralMeshSubset(QQmlListProperty<ProceduralMeshSubset> *list, ProceduralMeshSubset *subset)
511{
512 if (subset == nullptr)
513 return;
514 ProceduralMesh *self = static_cast<ProceduralMesh *>(list->object);
515 self->m_subsets.push_back(subset);
516
517 connect(subset, &ProceduralMeshSubset::isDirty, self, &ProceduralMesh::requestUpdate);
518 connect(subset, &QObject::destroyed, self, &ProceduralMesh::subsetDestroyed);
519
520 self->requestUpdate();
521}
522
523ProceduralMeshSubset *ProceduralMesh::qmlProceduralMeshSubsetAt(QQmlListProperty<ProceduralMeshSubset> *list, qsizetype index)
524{
525 ProceduralMesh *self = static_cast<ProceduralMesh *>(list->object);
526 return self->m_subsets.at(index);
527
528}
529
530qsizetype ProceduralMesh::qmlProceduralMeshSubsetCount(QQmlListProperty<ProceduralMeshSubset> *list)
531{
532 ProceduralMesh *self = static_cast<ProceduralMesh *>(list->object);
533 return self->m_subsets.count();
534}
535
536void ProceduralMesh::qmlClearProceduralMeshSubset(QQmlListProperty<ProceduralMeshSubset> *list)
537{
538 ProceduralMesh *self = static_cast<ProceduralMesh *>(list->object);
539 self->m_subsets.clear();
540 self->requestUpdate();
541}
542
543QList<unsigned int> ProceduralMesh::indexes() const
544{
545 return m_indexes;
546}
547
548void ProceduralMesh::setIndexes(const QList<unsigned int> &newIndexes)
549{
550 if (m_indexes == newIndexes)
551 return;
552 m_indexes = newIndexes;
553 Q_EMIT indexesChanged();
554 requestUpdate();
555}
556
557QList<QVector3D> ProceduralMesh::normals() const
558{
559 return m_normals;
560}
561
562void ProceduralMesh::setNormals(const QList<QVector3D> &newNormals)
563{
564 if (m_normals == newNormals)
565 return;
566 m_normals = newNormals;
567 Q_EMIT normalsChanged();
568 requestUpdate();
569}
570
571QList<QVector3D> ProceduralMesh::tangents() const
572{
573 return m_tangents;
574}
575
576void ProceduralMesh::setTangents(const QList<QVector3D> &newTangents)
577{
578 if (m_tangents == newTangents)
579 return;
580 m_tangents = newTangents;
581 Q_EMIT tangentsChanged();
582 requestUpdate();
583}
584
585QList<QVector3D> ProceduralMesh::binormals() const
586{
587 return m_binormals;
588}
589
590void ProceduralMesh::setBinormals(const QList<QVector3D> &newBinormals)
591{
592 if (m_binormals == newBinormals)
593 return;
594 m_binormals = newBinormals;
595 Q_EMIT binormalsChanged();
596 requestUpdate();
597}
598
599QList<QVector2D> ProceduralMesh::uv0s() const
600{
601 return m_uv0s;
602}
603
604void ProceduralMesh::setUv0s(const QList<QVector2D> &newUv0s)
605{
606 if (m_uv0s == newUv0s)
607 return;
608 m_uv0s = newUv0s;
609 Q_EMIT uv0sChanged();
610 requestUpdate();
611}
612
613QList<QVector2D> ProceduralMesh::uv1s() const
614{
615 return m_uv1s;
616}
617
618void ProceduralMesh::setUv1s(const QList<QVector2D> &newUv1s)
619{
620 if (m_uv1s == newUv1s)
621 return;
622 m_uv1s = newUv1s;
623 Q_EMIT uv1sChanged();
624 requestUpdate();
625}
626
627QList<QVector4D> ProceduralMesh::colors() const
628{
629 return m_colors;
630}
631
632void ProceduralMesh::setColors(const QList<QVector4D> &newColors)
633{
634 if (m_colors == newColors)
635 return;
636 m_colors = newColors;
637 Q_EMIT colorsChanged();
638 requestUpdate();
639}
640
641QList<QVector4D> ProceduralMesh::joints() const
642{
643 return m_joints;
644}
645
646void ProceduralMesh::setJoints(const QList<QVector4D> &newJoints)
647{
648 if (m_joints == newJoints)
649 return;
650 m_joints = newJoints;
651 Q_EMIT jointsChanged();
652 requestUpdate();
653}
654
655QList<QVector4D> ProceduralMesh::weights() const
656{
657 return m_weights;
658}
659
660void ProceduralMesh::setWeights(const QList<QVector4D> &newWeights)
661{
662 if (m_weights == newWeights)
663 return;
664 m_weights = newWeights;
665 Q_EMIT weightsChanged();
666 requestUpdate();
667}
668
669QQmlListProperty<ProceduralMeshSubset> ProceduralMesh::subsets()
670{
671 return QQmlListProperty<ProceduralMeshSubset>(this,
672 nullptr,
673 ProceduralMesh::qmlAppendProceduralMeshSubset,
674 ProceduralMesh::qmlProceduralMeshSubsetCount,
675 ProceduralMesh::qmlProceduralMeshSubsetAt,
676 ProceduralMesh::qmlClearProceduralMeshSubset);
677}
678
679int ProceduralMeshSubset::offset() const
680{
681 return m_offset;
682}
683
684void ProceduralMeshSubset::setOffset(int newOffset)
685{
686 if (m_offset == newOffset)
687 return;
688
689 m_offset = newOffset;
690 Q_EMIT offsetChanged();
691 Q_EMIT isDirty();
692}
693
694int ProceduralMeshSubset::count() const
695{
696 return m_count;
697}
698
699void ProceduralMeshSubset::setCount(int newCount)
700{
701 if (m_count == newCount)
702 return;
703
704 m_count = newCount;
705 Q_EMIT countChanged();
706 Q_EMIT isDirty();
707}
708
709QString ProceduralMeshSubset::name() const
710{
711 return m_name;
712}
713
714void ProceduralMeshSubset::setName(const QString &newName)
715{
716 if (m_name == newName)
717 return;
718
719 m_name = newName;
720 Q_EMIT nameChanged();
721 Q_EMIT isDirty();
722}
723
724QT_END_NAMESPACE
Combined button and popup list for selecting options.