8#include <QtCore/qdir.h>
9#include <QtQml/qqmlfile.h>
11#include <QtQuick3D/private/qquick3dobject_p.h>
12#include <QtQuick3D/private/qquick3dgeometry_p.h>
14#include <QtQuick3DUtils/private/qssgutils_p.h>
15#include <QtQuick3DRuntimeRender/private/qssgrenderparticles_p.h>
16#include <QtQuick3DRuntimeRender/private/qssgrendergeometry_p.h>
17#include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h>
18#include <QtQuick3DUtils/private/qssgmesh_p.h>
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
57QQuick3DParticleModelBlendParticle::QQuick3DParticleModelBlendParticle(QQuick3DNode *parent)
58 : QQuick3DParticle(*
new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::ModelBlendParticle), parent)
60 setFadeInEffect(QQuick3DParticle::FadeNone);
61 setFadeOutEffect(QQuick3DParticle::FadeNone);
62 QQuick3DParticle::doSetMaxAmount(0);
65QQuick3DParticleModelBlendParticle::~QQuick3DParticleModelBlendParticle()
68 delete m_modelGeometry;
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94QQmlComponent *QQuick3DParticleModelBlendParticle::delegate()
const
99void QQuick3DParticleModelBlendParticle::setDelegate(QQmlComponent *delegate)
101 if (delegate == m_delegate)
103 m_delegate = delegate;
107 Q_EMIT delegateChanged();
111
112
113
114
115
116
117QQuick3DNode *QQuick3DParticleModelBlendParticle::endNode()
const
123
124
125
126
127
128
129
130
131
132
133
135
136
137
138
139QQuick3DParticleModelBlendParticle::ModelBlendMode QQuick3DParticleModelBlendParticle::modelBlendMode()
const
141 return m_modelBlendMode;
145
146
147
148
149
150
151
152
153int QQuick3DParticleModelBlendParticle::endTime()
const
159
160
161
162
163
164
165
166
167
168QQuick3DNode *QQuick3DParticleModelBlendParticle::activationNode()
const
170 return m_activationNode;
174
175
176
177
178
179
180
181
182
183
184
186
187
188
189
190QQuick3DParticleModelBlendParticle::ModelBlendEmitMode QQuick3DParticleModelBlendParticle::emitMode()
const
195void QQuick3DParticleModelBlendParticle::setEndNode(QQuick3DNode *node)
197 if (m_endNode == node)
200 QObject::disconnect(
this);
205 QObject::connect(m_endNode, &QQuick3DNode::positionChanged,
this, &QQuick3DParticleModelBlendParticle::handleEndNodeChanged);
206 QObject::connect(m_endNode, &QQuick3DNode::rotationChanged,
this, &QQuick3DParticleModelBlendParticle::handleEndNodeChanged);
207 QObject::connect(m_endNode, &QQuick3DNode::scaleChanged,
this, &QQuick3DParticleModelBlendParticle::handleEndNodeChanged);
209 handleEndNodeChanged();
210 Q_EMIT endNodeChanged();
213void QQuick3DParticleModelBlendParticle::setModelBlendMode(ModelBlendMode mode)
215 if (m_modelBlendMode == mode)
217 m_modelBlendMode = mode;
219 Q_EMIT modelBlendModeChanged();
222void QQuick3DParticleModelBlendParticle::setEndTime(
int endTime)
224 if (endTime == m_endTime)
227 Q_EMIT endTimeChanged();
230void QQuick3DParticleModelBlendParticle::setActivationNode(QQuick3DNode *activationNode)
232 if (m_activationNode == activationNode)
235 m_activationNode = activationNode;
236 Q_EMIT activationNodeChanged();
239void QQuick3DParticleModelBlendParticle::setEmitMode(ModelBlendEmitMode emitMode)
241 if (m_emitMode == emitMode)
244 m_emitMode = emitMode;
245 Q_EMIT emitModeChanged();
248void QQuick3DParticleModelBlendParticle::regenerate()
253 if (!isComponentComplete())
259 if (QQuick3DParticleSystem::isGloballyDisabled())
262 auto *obj = m_delegate->create(m_delegate->creationContext());
264 m_model = qobject_cast<QQuick3DModel *>(obj);
267 auto *psystem = QQuick3DParticle::system();
268 m_model->setParent(psystem);
269 m_model->setParentItem(psystem);
273 handleEndNodeChanged();
278 QString src = source;
279 if (source.startsWith(QLatin1Char(
'#'))) {
280 src = QSSGBufferManager::primitivePath(source);
281 src.prepend(QLatin1String(
":/"));
283 src = QDir::cleanPath(src);
284 if (src.startsWith(QLatin1String(
"qrc:/")))
288 if (!file.open(QFile::ReadOnly))
290 return QSSGMesh::Mesh::loadMesh(&file);
295 const quint8 *vertex = srcVertices + idx * vertexStride;
296 return *
reinterpret_cast<
const QVector3D *>(vertex + posOffset);
299static float calcTriangleRadius(
const QVector3D ¢er,
const QVector3D &p0,
const QVector3D &p1,
const QVector3D &p2)
301 return qMax(center.distanceToPoint(p1), qMax(center.distanceToPoint(p2), center.distanceToPoint(p0)));
305 QVector<QVector3D> ¢erData,
306 float &maxTriangleRadius,
307 const QByteArray &vertexBufferData,
308 quint32 vertexStride,
310 const QByteArray &indexBufferData,
312 quint32 primitiveCount)
314 const quint8 *srcVertices =
reinterpret_cast<
const quint8 *>(vertexBufferData.data());
315 quint8 *dst =
reinterpret_cast<quint8 *>(unindexedVertexData.data());
316 const quint16 *indexData16 =
reinterpret_cast<
const quint16 *>(indexBufferData.begin());
317 const quint32 *indexData32 =
reinterpret_cast<
const quint32 *>(indexBufferData.begin());
318 const float c_div3 = 1.0f / 3.0f;
319 for (quint32 i = 0; i < primitiveCount; i++) {
322 i0 = indexData16[3 * i];
323 i1 = indexData16[3 * i + 1];
324 i2 = indexData16[3 * i + 2];
326 i0 = indexData32[3 * i];
327 i1 = indexData32[3 * i + 1];
328 i2 = indexData32[3 * i + 2];
330 QVector3D p0 = getPosition(srcVertices, i0, vertexStride, posOffset);
331 QVector3D p1 = getPosition(srcVertices, i1, vertexStride, posOffset);
332 QVector3D p2 = getPosition(srcVertices, i2, vertexStride, posOffset);
333 QVector3D center = (p0 + p1 + p2) * c_div3;
334 centerData[i] = center;
335 maxTriangleRadius = qMax(maxTriangleRadius, calcTriangleRadius(center, p0, p1, p2));
336 memcpy(dst, srcVertices + i0 * vertexStride, vertexStride);
338 memcpy(dst, srcVertices + i1 * vertexStride, vertexStride);
340 memcpy(dst, srcVertices + i2 * vertexStride, vertexStride);
346 float &maxTriangleRadius,
347 const QByteArray &vertexBufferData,
348 quint32 vertexStride,
350 quint32 primitiveCount)
352 const quint8 *srcVertices =
reinterpret_cast<
const quint8 *>(vertexBufferData.data());
353 const float c_div3 = 1.0f / 3.0f;
354 for (quint32 i = 0; i < primitiveCount; i++) {
355 QVector3D p0 = getPosition(srcVertices, 3 * i, vertexStride, posOffset);
356 QVector3D p1 = getPosition(srcVertices, 3 * i + 1, vertexStride, posOffset);
357 QVector3D p2 = getPosition(srcVertices, 3 * i + 2, vertexStride, posOffset);
358 QVector3D center = (p0 + p1 + p2) * c_div3;
359 centerData[i] = center;
360 maxTriangleRadius = qMax(maxTriangleRadius, calcTriangleRadius(center, p0, p1, p2));
364void QQuick3DParticleModelBlendParticle::updateParticles()
366 m_maxTriangleRadius = 0.f;
371 if (m_model->geometry()) {
372 QQuick3DGeometry *geometry = m_model->geometry();
373 if (geometry->primitiveType() != QQuick3DGeometry::PrimitiveType::Triangles) {
374 qWarning () <<
"ModelBlendParticle3D: Invalid geometry primitive type, must be Triangles. ";
377 auto vertexBuffer = geometry->vertexData();
378 auto indexBuffer = geometry->indexData();
380 if (!vertexBuffer.size()) {
381 qWarning () <<
"ModelBlendParticle3D: Invalid geometry, vertexData is empty. ";
385 const auto attributeBySemantic = [&](
const QQuick3DGeometry *geometry, QQuick3DGeometry::Attribute::Semantic semantic) {
386 for (
int i = 0; i < geometry->attributeCount(); i++) {
387 const auto attr = geometry->attribute(i);
388 if (attr.semantic == semantic)
392 return QQuick3DGeometry::Attribute();
395 if (indexBuffer.size()) {
396 m_modelGeometry =
new QQuick3DGeometry;
398 m_modelGeometry->setBounds(geometry->boundsMin(), geometry->boundsMax());
399 m_modelGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
400 m_modelGeometry->setStride(geometry->stride());
402 for (
int i = 0; i < geometry->attributeCount(); i++) {
403 auto attr = geometry->attribute(i);
404 if (attr.semantic != QQuick3DGeometry::Attribute::IndexSemantic)
405 m_modelGeometry->addAttribute(attr);
409 QByteArray unindexedVertexData;
410 quint32 primitiveCount = indexBuffer.size();
411 auto indexAttribute = attributeBySemantic(geometry, QQuick3DGeometry::Attribute::IndexSemantic);
412 bool u16IndexType = indexAttribute.componentType == QQuick3DGeometry::Attribute::U16Type;
416 primitiveCount /= 12;
418 unindexedVertexData.resize(geometry->stride() * primitiveCount * 3);
419 m_centerData.resize(primitiveCount);
420 m_particleCount = primitiveCount;
421 copyToUnindexedVertices(unindexedVertexData,
426 attributeBySemantic(geometry, QQuick3DGeometry::Attribute::PositionSemantic).offset,
431 m_modelGeometry->setVertexData(unindexedVertexData);
432 m_model->setGeometry(m_modelGeometry);
435 quint32 primitiveCount = vertexBuffer.size() / geometry->stride() / 3;
436 m_centerData.resize(primitiveCount);
437 m_particleCount = primitiveCount;
438 getVertexCenterData(m_centerData,
442 attributeBySemantic(geometry, QQuick3DGeometry::Attribute::PositionSemantic).offset,
446 const QQmlContext *context = qmlContext(
this);
447 QString src = m_model->source().toString();
448 if (context && !src.startsWith(QLatin1Char(
'#')))
449 src = QQmlFile::urlToLocalFileOrQrc(context->resolvedUrl(m_model->source()));
450 QSSGMesh::Mesh mesh = loadModelBlendParticleMesh(src);
451 if (!mesh.isValid()) {
452 qWarning () <<
"ModelBlendParticle3D: Unable to load mesh: " << src;
455 if (mesh.drawMode() != QSSGMesh::Mesh::DrawMode::Triangles) {
456 qWarning () <<
"ModelBlendParticle3D: Invalid mesh primitive type, must be Triangles. ";
460 m_modelGeometry =
new QQuick3DGeometry;
462 const auto vertexBuffer = mesh.vertexBuffer();
463 const auto indexBuffer = mesh.indexBuffer();
465 const auto entryOffset = [&](
const QSSGMesh::Mesh::VertexBuffer &vb,
const QByteArray &name) ->
int {
466 for (
const auto &e : vb.entries) {
467 if (e.name == name) {
468 Q_ASSERT(e.componentType == QSSGMesh::Mesh::ComponentType::Float32);
475 const auto toAttribute = [&](
const QSSGMesh::Mesh::VertexBufferEntry &e) -> QQuick3DGeometry::Attribute {
476 QQuick3DGeometry::Attribute a;
477 a.componentType = QQuick3DGeometryPrivate::toComponentType(e.componentType);
479 a.semantic = QQuick3DGeometryPrivate::semanticFromName(e.name);
483 const auto indexedPrimitiveCount = [&](
const QSSGMesh::Mesh::IndexBuffer &indexBuffer) -> quint32 {
484 if (indexBuffer.componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16)
485 return quint32(indexBuffer.data.size() /
sizeof(quint16) / 3);
486 return quint32(indexBuffer.data.size() /
sizeof(quint32) / 3);
489 if (indexBuffer.data.size()) {
491 QByteArray unindexedVertexData;
492 quint32 primitiveCount = indexedPrimitiveCount(indexBuffer);
493 bool u16IndexType = indexBuffer.componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16;
494 unindexedVertexData.resize(vertexBuffer.stride * primitiveCount * 3);
495 m_centerData.resize(primitiveCount);
496 m_particleCount = primitiveCount;
498 copyToUnindexedVertices(unindexedVertexData,
503 entryOffset(vertexBuffer, QByteArray(QSSGMesh::MeshInternal::getPositionAttrName())),
507 m_modelGeometry->setBounds(mesh.subsets().first().bounds.min, mesh.subsets().first().bounds.max);
508 m_modelGeometry->setStride(vertexBuffer.stride);
509 m_modelGeometry->setVertexData(unindexedVertexData);
510 m_modelGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
513 quint32 primitiveCount = vertexBuffer.data.size() / vertexBuffer.stride / 3;
514 m_centerData.resize(primitiveCount);
515 m_particleCount = primitiveCount;
516 getVertexCenterData(m_centerData,
520 entryOffset(vertexBuffer, QByteArray(QSSGMesh::MeshInternal::getPositionAttrName())),
522 m_modelGeometry->setBounds(mesh.subsets().first().bounds.min, mesh.subsets().first().bounds.max);
523 m_modelGeometry->setStride(vertexBuffer.stride);
524 m_modelGeometry->setVertexData(vertexBuffer.data);
525 m_modelGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
527 for (
auto &e : vertexBuffer.entries)
528 m_modelGeometry->addAttribute(toAttribute(e));
529 for (
auto &s : mesh.subsets())
530 m_modelGeometry->addSubset(s.offset, s.count, s.bounds.min, s.bounds.max, s.name);
532 m_model->setSource({});
533 m_model->setGeometry(m_modelGeometry);
536 QMatrix4x4 transform = m_model->sceneTransform();
537 if (m_model->parentNode())
538 transform = m_model->parentNode()->sceneTransform().inverted() * transform;
539 const QVector3D scale = QSSGUtils::mat44::getScale(transform);
541 const float scaleMax = qMax(scale.x(), qMax(scale.y(), scale.z()));
542 m_maxTriangleRadius *= scaleMax;
544 m_triangleParticleData.resize(m_particleCount);
545 m_particleData.resize(m_particleCount);
546 m_particleData.fill({});
547 for (
int i = 0; i < m_particleCount; i++) {
548 m_triangleParticleData[i].center = m_centerData[i];
549 m_centerData[i] = transform.map(m_centerData[i]);
550 if (m_modelBlendMode == Construct) {
551 m_triangleParticleData[i].size = 0.0f;
553 m_triangleParticleData[i].size = 1.0f;
554 m_triangleParticleData[i].position = m_centerData[i];
557 QQuick3DParticle::doSetMaxAmount(m_particleCount);
560QSSGRenderGraphObject *QQuick3DParticleModelBlendParticle::updateSpatialNode(QSSGRenderGraphObject *node)
564 auto *spatialNode = QQuick3DObjectPrivate::get(m_model)->spatialNode;
566 spatialNode = QQuick3DObjectPrivate::updateSpatialNode(m_model,
nullptr);
567 QQuick3DObjectPrivate::get(m_model)->spatialNode = spatialNode;
568 Q_QUICK3D_PROFILE_ASSIGN_ID_SG(
this, spatialNode);
570#if QT_CONFIG(qml_debug)
571 if (m_modelGeometry) {
572 auto *geometrySpatialNode = QQuick3DObjectPrivate::get(m_modelGeometry)->spatialNode;
573 if (geometrySpatialNode)
574 Q_QUICK3D_PROFILE_ASSIGN_ID_SG(
this, geometrySpatialNode);
578 QSSGRenderModel *model =
static_cast<QSSGRenderModel *>(spatialNode);
580 if (!model->particleBuffer) {
581 QSSGParticleBuffer *buffer = model->particleBuffer =
new QSSGParticleBuffer;
582 buffer->resize(m_particleCount,
sizeof(QSSGTriangleParticle));
584 QQuick3DParticleSystem *psystem = QQuick3DParticle::system();
585 QMatrix4x4 particleMatrix = psystem->sceneTransform().inverted() * m_model->sceneTransform();
586 model->particleMatrix = particleMatrix.inverted();
587 model->hasTransparency = fadeInEffect() == QQuick3DParticle::FadeOpacity || fadeOutEffect() == QQuick3DParticle::FadeOpacity;
588 updateParticleBuffer(model->particleBuffer, psystem->sceneTransform());
593void QQuick3DParticleModelBlendParticle::componentComplete()
595 if (!system() && qobject_cast<QQuick3DParticleSystem *>(parentItem()))
596 setSystem(qobject_cast<QQuick3DParticleSystem *>(parentItem()));
599 QQuick3DObject::componentComplete();
603void QQuick3DParticleModelBlendParticle::doSetMaxAmount(
int)
605 qWarning() <<
"ModelBlendParticle3D.maxAmount: Unable to set maximum amount, because it is set from the model.";
609int QQuick3DParticleModelBlendParticle::nextCurrentIndex(
const QQuick3DParticleEmitter *emitter)
611 if (!m_perEmitterData.contains(emitter)) {
612 m_perEmitterData.insert(emitter, PerEmitterData());
613 auto &perEmitter = m_perEmitterData[emitter];
614 perEmitter.emitter = emitter;
615 perEmitter.emitterIndex = m_nextEmitterIndex++;
617 auto &perEmitter = m_perEmitterData[emitter];
618 int index = QQuick3DParticle::nextCurrentIndex(emitter);
619 if (m_triangleParticleData[index].emitterIndex != perEmitter.emitterIndex) {
620 if (m_triangleParticleData[index].emitterIndex >= 0)
621 perEmitterData(m_triangleParticleData[index].emitterIndex).particleCount--;
622 perEmitter.particleCount++;
624 m_triangleParticleData[index].emitterIndex = perEmitter.emitterIndex;
629void QQuick3DParticleModelBlendParticle::setParticleData(
int particleIndex,
630 const QVector3D &position,
631 const QVector3D &rotation,
632 const QVector4D &color,
633 float size,
float age)
635 auto &dst = m_triangleParticleData[particleIndex];
636 dst = {position, rotation, dst.center, color, age, size, dst.emitterIndex};
637 m_dataChanged =
true;
640QQuick3DParticleModelBlendParticle::PerEmitterData &QQuick3DParticleModelBlendParticle::perEmitterData(
int emitterIndex)
642 for (
auto &perEmitter : m_perEmitterData) {
643 if (perEmitter.emitterIndex == emitterIndex)
646 return n_noPerEmitterData;
649void QQuick3DParticleModelBlendParticle::updateParticleBuffer(QSSGParticleBuffer *buffer,
const QMatrix4x4 &sceneTransform)
651 const auto &particles = m_triangleParticleData;
653 if (!buffer || !m_dataChanged)
656 const int particleCount = m_particleCount;
658 char *dest = buffer->pointer();
659 const TriangleParticleData *src = particles.data();
660 const int pps = buffer->particlesPerSlice();
661 const int ss = buffer->sliceStride();
662 const int slices = buffer->sliceCount();
663 const float c_degToRad =
float(M_PI / 180.0f);
666 for (
int s = 0; s < slices; s++) {
667 QSSGTriangleParticle *dp =
reinterpret_cast<QSSGTriangleParticle *>(dest);
668 for (
int p = 0; p < pps && i < particleCount; ) {
669 if (src->size > 0.0f)
670 bounds.include(src->position);
671 dp->position = src->position;
672 dp->rotation = src->rotation * c_degToRad;
673 dp->color = src->color;
675 dp->center = src->center;
676 dp->size = src->size;
685 bounds.fatten(m_maxTriangleRadius);
686 bounds.transform(sceneTransform);
687 buffer->setBounds(bounds);
688 m_dataChanged =
false;
691void QQuick3DParticleModelBlendParticle::itemChange(QQuick3DObject::ItemChange change,
692 const QQuick3DObject::ItemChangeData &value)
694 QQuick3DObject::itemChange(change, value);
695 if (change == ItemParentHasChanged && value.sceneManager)
699void QQuick3DParticleModelBlendParticle::reset()
701 QQuick3DParticle::reset();
702 if (m_particleCount) {
703 for (
int i = 0; i < m_particleCount; i++) {
704 if (m_modelBlendMode == Construct) {
705 m_triangleParticleData[i].size = 0.0f;
707 m_triangleParticleData[i].size = 1.0f;
708 m_triangleParticleData[i].position = m_triangleParticleData[i].center;
714QVector3D QQuick3DParticleModelBlendParticle::particleCenter(
int particleIndex)
const
716 return m_centerData[particleIndex];
719bool QQuick3DParticleModelBlendParticle::lastParticle()
const
721 return m_currentIndex >= m_maxAmount - 1;
726 float x = qDegreesToRadians(eulerRotation.x());
727 float y = qDegreesToRadians(eulerRotation.y());
728 float z = qDegreesToRadians(eulerRotation.z());
741 ret(1,0) = bd * e + a * f;
742 ret(1,1) = a * e - bd * f;
744 ret(2,0) = b * f - ad * e;
745 ret(2,1) = ad * f + b * e;
750void QQuick3DParticleModelBlendParticle::handleEndNodeChanged()
752 if (m_endNode && m_model) {
753 if (!m_model->rotation().isIdentity()) {
755 QMatrix3x3 r1 = qt_fromEulerRotation(m_endNode->eulerRotation());
756 QMatrix3x3 r2 = m_model->rotation().toRotationMatrix();
757 QMatrix3x3 r = r2 * r1.transposed() * r2.transposed();
758 m_endNodeRotation = m_endNode->eulerRotation();
759 m_endRotationMatrix = QMatrix4x4(r);
761 m_endNodeRotation = m_endNode->eulerRotation();
762 m_endRotationMatrix = QMatrix4x4(m_endNode->rotation().toRotationMatrix().transposed());
764 m_endNodePosition = m_endNode->position();
765 m_endNodeScale = m_endNode->scale();
767 m_endNodePosition = QVector3D();
768 m_endNodeRotation = QVector3D();
769 m_endNodeScale = QVector3D(1.0f, 1.0f, 1.0f);
770 m_endRotationMatrix.setToIdentity();
774QVector3D QQuick3DParticleModelBlendParticle::particleEndPosition(
int idx)
const
776 return m_endRotationMatrix.map(QVector3D(m_endNodeScale * m_centerData[idx])) + m_endNodePosition;
779QVector3D QQuick3DParticleModelBlendParticle::particleEndRotation(
int)
const
781 return m_endNodeRotation;
784int QQuick3DParticleModelBlendParticle::randomIndex(
int particleIndex)
786 if (m_randomParticles.isEmpty()) {
787 m_randomParticles.resize(m_maxAmount);
788 for (
int i = 0; i < m_maxAmount; i++)
789 m_randomParticles[i] = i;
792 QRandomGenerator rand(system()->rand()->generator());
793 for (
int i = 0; i < m_maxAmount; i++) {
794 int ridx = rand.generate() % m_maxAmount;
796 qSwap(m_randomParticles[i], m_randomParticles[ridx]);
799 return m_randomParticles[particleIndex];
static float calcTriangleRadius(const QVector3D ¢er, const QVector3D &p0, const QVector3D &p1, const QVector3D &p2)
static QVector3D getPosition(const quint8 *srcVertices, quint32 idx, quint32 vertexStride, quint32 posOffset)
static void getVertexCenterData(QVector< QVector3D > ¢erData, float &maxTriangleRadius, const QByteArray &vertexBufferData, quint32 vertexStride, quint32 posOffset, quint32 primitiveCount)
static void copyToUnindexedVertices(QByteArray &unindexedVertexData, QVector< QVector3D > ¢erData, float &maxTriangleRadius, const QByteArray &vertexBufferData, quint32 vertexStride, quint32 posOffset, const QByteArray &indexBufferData, bool u16Indices, quint32 primitiveCount)
static QMatrix3x3 qt_fromEulerRotation(const QVector3D &eulerRotation)
static QSSGMesh::Mesh loadModelBlendParticleMesh(const QString &source)