Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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
13
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;
212 requestUpdate();
213}
214
216{
217 return m_primitiveMode;
218}
219
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;
240 requestUpdate();
241}
242
243void ProceduralMesh::requestUpdate()
244{
245 if (!m_updateRequested) {
246 QMetaObject::invokeMethod(this, "updateGeometry", 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(Attribute::Semantic::PositionSemantic, offset, Attribute::ComponentType::F32Type);
280 offset += 3 * sizeof(float);
281 }
282
283 if (hasNormals) {
284 addAttribute(Attribute::Semantic::NormalSemantic, offset, Attribute::ComponentType::F32Type);
285 offset += 3 * sizeof(float);
286 }
287
288 if (hasTangents) {
289 addAttribute(Attribute::Semantic::TangentSemantic, offset, Attribute::ComponentType::F32Type);
290 offset += 3 * sizeof(float);
291 }
292
293 if (hasBinormals) {
294 addAttribute(Attribute::Semantic::BinormalSemantic, offset, Attribute::ComponentType::F32Type);
295 offset += 3 * sizeof(float);
296 }
297
298 if (hasUV0s) {
299 addAttribute(Attribute::Semantic::TexCoord0Semantic, offset, Attribute::ComponentType::F32Type);
300 offset += 2 * sizeof(float);
301 }
302
303 if (hasUV1s) {
304 addAttribute(Attribute::Semantic::TexCoord1Semantic, offset, Attribute::ComponentType::F32Type);
305 offset += 2 * sizeof(float);
306 }
307
308 if (hasColors) {
309 addAttribute(Attribute::Semantic::ColorSemantic, offset, Attribute::ComponentType::F32Type);
310 offset += 4 * sizeof(float);
311 }
312
313 if (hasJoints) {
314 addAttribute(Attribute::Semantic::JointSemantic, offset, Attribute::ComponentType::F32Type);
315 offset += 4 * sizeof(float);
316 }
317
318 if (hasWeights) {
319 addAttribute(Attribute::Semantic::WeightSemantic, offset, Attribute::ComponentType::F32Type);
320 offset += 4 * sizeof(float);
321 }
322
323 if (hasIndexes)
324 addAttribute(Attribute::Semantic::IndexSemantic, 0, Attribute::ComponentType::U32Type);
325
326 // Set up the vertex buffer
327 const int stride = offset;
328 const qsizetype bufferSize = expectedLength * stride;
330
331 QVector<float> vertexBufferData;
332 vertexBufferData.reserve(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(position.x());
342 vertexBufferData.append(position.y());
343 vertexBufferData.append(position.z());
344 minBounds.setX(qMin(minBounds.x(), position.x()));
345 maxBounds.setX(qMax(maxBounds.x(), position.x()));
346 minBounds.setY(qMin(minBounds.y(), position.y()));
347 maxBounds.setY(qMax(maxBounds.y(), position.y()));
348 minBounds.setZ(qMin(minBounds.z(), position.z()));
349 maxBounds.setZ(qMax(maxBounds.z(), position.z()));
350 }
351 if (hasNormals) {
352 const auto &normal = m_normals[i];
353 vertexBufferData.append(normal.x());
354 vertexBufferData.append(normal.y());
355 vertexBufferData.append(normal.z());
356 }
357
358 if (hasBinormals) {
359 const auto &binormal = m_binormals[i];
360 vertexBufferData.append(binormal.x());
361 vertexBufferData.append(binormal.y());
362 vertexBufferData.append(binormal.z());
363 }
364
365 if (hasTangents) {
366 const auto &tangent = m_tangents[i];
367 vertexBufferData.append(tangent.x());
368 vertexBufferData.append(tangent.y());
369 vertexBufferData.append(tangent.z());
370 }
371
372 if (hasUV0s) {
373 const auto &uv0 = m_uv0s[i];
374 vertexBufferData.append(uv0.x());
375 vertexBufferData.append(uv0.y());
376 }
377
378 if (hasUV1s) {
379 const auto &uv1 = m_uv1s[i];
380 vertexBufferData.append(uv1.x());
381 vertexBufferData.append(uv1.y());
382 }
383
384 if (hasColors) {
385 const auto &color = m_colors[i];
386 vertexBufferData.append(color.x());
387 vertexBufferData.append(color.y());
388 vertexBufferData.append(color.z());
389 vertexBufferData.append(color.w());
390 }
391
392 if (hasJoints) {
393 const auto &joint = m_joints[i];
394 vertexBufferData.append(joint.x());
395 vertexBufferData.append(joint.y());
396 vertexBufferData.append(joint.z());
397 vertexBufferData.append(joint.w());
398 }
399
400 if (hasWeights) {
401 const auto &weight = m_weights[i];
402 vertexBufferData.append(weight.x());
403 vertexBufferData.append(weight.y());
404 vertexBufferData.append(weight.z());
405 vertexBufferData.append(weight.w());
406 }
407 }
408
409 setBounds(minBounds, 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(indexLength * sizeof(unsigned int));
418 for (qsizetype i = 0; i < indexLength; ++i) {
419 const auto &index = m_indexes[i];
420 indexBuffer.append(reinterpret_cast<const char *>(&index), 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(subsetMinBounds.x(), position.x()));
448 subsetMaxBounds.setX(qMax(subsetMaxBounds.x(), position.x()));
449 subsetMinBounds.setY(qMin(subsetMinBounds.y(), position.y()));
450 subsetMaxBounds.setY(qMax(subsetMaxBounds.y(), position.y()));
451 subsetMinBounds.setZ(qMin(subsetMinBounds.z(), position.z()));
452 subsetMaxBounds.setZ(qMax(subsetMaxBounds.z(), position.z()));
453 } else {
454 outOfRange = true;
455 break;
456 }
457 }
458 }
459 if (!outOfRange)
460 addSubset(subset->offset(), subset->count(), subsetMinBounds, subsetMaxBounds, subset->name());
461 else
462 qWarning("Skipping invalid subset: Out of Range");
463 }
464 }
465
466 update();
467}
468
469void ProceduralMesh::subsetDestroyed(QObject *subset)
470{
471 if (m_subsets.removeAll(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(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(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(subset);
502
503 connect(subset, &ProceduralMeshSubset::isDirty, self, &ProceduralMesh::requestUpdate);
504 connect(subset, &QObject::destroyed, self, &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(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;
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;
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;
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;
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;
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;
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;
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;
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;
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
666{
667 return m_offset;
668}
669
671{
672 if (m_offset == newOffset)
673 return;
674
675 m_offset = newOffset;
677 Q_EMIT isDirty();
678}
679
681{
682 return m_count;
683}
684
686{
687 if (m_count == newCount)
688 return;
689
690 m_count = newCount;
692 Q_EMIT isDirty();
693}
694
696{
697 return m_name;
698}
699
701{
702 if (m_name == newName)
703 return;
704
705 m_name = newName;
707 Q_EMIT isDirty();
708}
709
void setCount(int newCount)
void setOffset(int newOffset)
void setName(const QString &newName)
void colorsChanged()
ProceduralMesh()
\qmlproperty List<QVector3D> ProceduralMesh::positions The positions attribute list.
void setUv1s(const QList< QVector2D > &newUv1s)
void weightsChanged()
void uv1sChanged()
QList< QVector2D > uv0s
void setWeights(const QList< QVector4D > &newWeights)
void jointsChanged()
void primitiveModeChanged()
QList< QVector4D > joints
void setPositions(const QList< QVector3D > &newPositions)
void positionsChanged()
QList< QVector3D > positions
void binormalsChanged()
void normalsChanged()
QList< QVector4D > colors
void setTangents(const QList< QVector3D > &newTangents)
QList< QVector3D > binormals
void setNormals(const QList< QVector3D > &newNormals)
void uv0sChanged()
void setJoints(const QList< QVector4D > &newJoints)
PrimitiveMode primitiveMode
void setBinormals(const QList< QVector3D > &newBinormals)
void setIndexes(const QList< unsigned int > &newIndexes)
void setUv0s(const QList< QVector2D > &newUv0s)
void setPrimitiveMode(PrimitiveMode newPrimitiveMode)
QQmlListProperty< ProceduralMeshSubset > subsets
QList< QVector3D > normals
QList< QVector4D > weights
QList< unsigned int > indexes
QList< QVector2D > uv1s
void indexesChanged()
void tangentsChanged()
QList< QVector3D > tangents
void setColors(const QList< QVector4D > &newColors)
\inmodule QtCore
Definition qbytearray.h:57
void reserve(qsizetype size)
Attempts to allocate memory for at least size bytes.
Definition qbytearray.h:634
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
qsizetype removeAll(const AT &t)
Definition qlist.h:592
void append(parameter_type t)
Definition qlist.h:458
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
Attribute::Semantic int int stride
Returns the byte stride of the vertex buffer.
void setPrimitiveType(PrimitiveType type)
Sets the primitive type used for rendering to type.
void setStride(int stride)
Sets the stride of the vertex buffer to stride, measured in bytes.
void addAttribute(Attribute::Semantic semantic, int offset, Attribute::ComponentType componentType)
Adds vertex attribute description.
void setVertexData(const QByteArray &data)
Sets the vertex buffer data.
Attribute::Semantic int offset
void clear()
Resets the geometry to its initial state, clearing previously set vertex and index data as well as at...
void setBounds(const QVector3D &min, const QVector3D &max)
Sets the bounding volume of the geometry to the cube defined by the points min and max.
void setIndexData(const QByteArray &data)
Sets the index buffer to data.
static QQuick3DObjectPrivate * get(QQuick3DObject *item)
@ TriangleFanTopology
Definition qrhi.h:1849
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1252
void push_back(QChar c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.h:957
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1226
qsizetype count(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4833
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
Combined button and popup list for selecting options.
@ QueuedConnection
QString self
Definition language.cpp:58
#define qWarning
Definition qlogging.h:166
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLuint index
[2]
GLuint GLuint GLfloat weight
const void GLsizei GLsizei stride
GLuint color
[2]
GLenum GLuint GLintptr offset
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define Q_EMIT
ptrdiff_t qsizetype
Definition qtypes.h:165
QList< int > list
[14]
aWidget window() -> setWindowTitle("New Window Title")
[2]
QNetworkAccessManager manager
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...