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

source code of qtquick3d/src/helpers/proceduralmesh.cpp