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

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