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
qquick3dparticlemodelblendparticle.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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
9
10#include <QtCore/qdir.h>
11#include <QtQml/qqmlfile.h>
12
13#include <QtQuick3D/private/qquick3dobject_p.h>
14#include <QtQuick3D/private/qquick3dgeometry_p.h>
15
16#include <QtQuick3DUtils/private/qssgutils_p.h>
17#include <QtQuick3DRuntimeRender/private/qssgrenderparticles_p.h>
18#include <QtQuick3DRuntimeRender/private/qssgrendergeometry_p.h>
19#include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h>
20#include <QtQuick3DUtils/private/qssgmesh_p.h>
21
23
24/*!
25 \qmltype ModelBlendParticle3D
26 \inherits Particle3D
27 \inqmlmodule QtQuick3D.Particles3D
28 \brief Blends particle effect with a 3D model.
29
30 The type provides a way to blend particle effect with a 3D model. The provided model needs to be
31 triangle-based. Each triangle in the model is converted into a particle, which are then used by
32 the emitter. Instead of particle shader, the model is shaded using the \l {Model::materials}{material}
33 specified in the model. The way the effect is blended is determined by the \l modelBlendMode.
34
35 The possible modes are:
36 \list
37 \li \b Construct, where the model gets constructed from the particles.
38 \li \b Explode, where the model gets converted into particles.
39 \li \b Transfer, where Construct and Explode are combined to create an effect where the model is
40 transferred from one place to another.
41 \endlist
42
43 By default the particles are emitted in the order they are specified in the model unless \l emitMode is set
44 to \c Random or \l emitMode is set to \c Activation and \l activationNode is set.
45
46 Some features defined in base class and emitters are not functional with this type:
47 \list
48 \li \l Particle3D::alignMode is not functional since the particles can be in arbitrary orientation
49 in the model.
50 \li\l Particle3D::sortMode is not functional since the particles are always rendered in the order
51 the primitives are specified in the model.
52 \li \l ParticleEmitter3D::depthBias is not functional since the model depth bias is used instead.
53 \endlist
54
55 \note The default \l {Particle3D::fadeInEffect}{fadeInEffect} and \l {Particle3D::fadeInEffect}{fadeOutEffect}
56 are \c Particle3D.FadeNone.
57*/
58
59QQuick3DParticleModelBlendParticle::QQuick3DParticleModelBlendParticle(QQuick3DNode *parent)
60 : QQuick3DParticle(*new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::ModelBlendParticle), parent)
61{
62 setFadeInEffect(QQuick3DParticle::FadeNone);
63 setFadeOutEffect(QQuick3DParticle::FadeNone);
64 QQuick3DParticle::doSetMaxAmount(0);
65}
66
67QQuick3DParticleModelBlendParticle::~QQuick3DParticleModelBlendParticle()
68{
69 delete m_model;
70 delete m_modelGeometry;
71}
72
73/*!
74 \qmlproperty Component ModelBlendParticle3D::delegate
75
76 The delegate provides a template defining the model for the ModelBlendParticle3D.
77
78 For example, using the default sphere model with default material
79
80 \qml
81 Component {
82 id: modelComponent
83 Model {
84 source: "#Sphere"
85 scale: Qt.vector3d(0.5, 0.5, 0.5)
86 materials: DefaultMaterial { diffuseColor: "red" }
87 }
88 }
89
90 ModelBlendParticle3D {
91 id: particleRedSphere
92 delegate: modelComponent
93 }
94 \endqml
95*/
96QQmlComponent *QQuick3DParticleModelBlendParticle::delegate() const
97{
98 return m_delegate;
99}
100
101void QQuick3DParticleModelBlendParticle::setDelegate(QQmlComponent *delegate)
102{
103 if (delegate == m_delegate)
104 return;
105 m_delegate = delegate;
106
107 reset();
108 regenerate();
109 Q_EMIT delegateChanged();
110}
111
112/*!
113 \qmlproperty Node ModelBlendParticle3D::endNode
114
115 This property holds the node that specifies the transformation for the model at the end
116 of particle effect. It defines the size, position and rotation where the model is constructed
117 when using the \c ModelBlendParticle3D.Construct and \c ModelBlendParticle3D.Transfer blend modes.
118*/
119QQuick3DNode *QQuick3DParticleModelBlendParticle::endNode() const
120{
121 return m_endNode;
122}
123
124/*!
125 \qmlproperty enumeration ModelBlendParticle3D::ModelBlendMode
126
127 Defines the blending mode for the particle effect.
128
129 \value ModelBlendParticle3D.Explode
130 The model gets exploded i.e. the particles are emitted from the position of the model.
131 \value ModelBlendParticle3D.Construct
132 The model gets constructed i.e the particles fly from the emitter and construct the model at the end.
133 \value ModelBlendParticle3D.Transfer
134 Combines Explode and Transfer for the same model.
135*/
136/*!
137 \qmlproperty ModelBlendMode ModelBlendParticle3D::modelBlendMode
138
139 This property holds blending mode for the particle effect.
140*/
141QQuick3DParticleModelBlendParticle::ModelBlendMode QQuick3DParticleModelBlendParticle::modelBlendMode() const
142{
143 return m_modelBlendMode;
144}
145
146/*!
147 \qmlproperty int ModelBlendParticle3D::endTime
148
149 This property holds the end time of the particle in milliseconds. The end time is used during construction
150 and defines duration from particle lifetime at the end where the effect is blended with
151 the model positions. Before the end time the particles positions are defined only by the
152 particle effect, but during end time the particle position is blended linearly with the model
153 end position.
154*/
155int QQuick3DParticleModelBlendParticle::endTime() const
156{
157 return m_endTime;
158}
159
160/*!
161 \qmlproperty Node ModelBlendParticle3D::activationNode
162
163 This property holds a node that activates particles and overrides the reqular emit routine.
164 The activation node can be used to control how the particles are emitted spatially when the
165 model is exploded/constructed from the particles.
166 The activation node emits a particle if the center of that particle is on the positive half
167 of the z-axis of the activation node. Animating the activation node to move trough the model
168 will cause the particles to be emitted sequentially along the path the activation node moves.
169*/
170QQuick3DNode *QQuick3DParticleModelBlendParticle::activationNode() const
171{
172 return m_activationNode;
173}
174
175/*!
176 \qmlproperty enumeration ModelBlendParticle3D::ModelBlendEmitMode
177
178 Defines the emit mode of the particles
179
180 \value ModelBlendParticle3D.Sequential
181 The particles are emitted in the order they are defined in the model.
182 \value ModelBlendParticle3D.Random
183 The particles are emitted in random order.
184 \value ModelBlendParticle3D.Activation
185 The particles are emitted when they are activated by the \l activationNode.
186*/
187/*!
188 \qmlproperty bool ModelBlendParticle3D::emitMode
189
190 This property holds the emit mode of the particles.
191*/
192QQuick3DParticleModelBlendParticle::ModelBlendEmitMode QQuick3DParticleModelBlendParticle::emitMode() const
193{
194 return m_emitMode;
195}
196
197void QQuick3DParticleModelBlendParticle::setEndNode(QQuick3DNode *node)
198{
199 if (m_endNode == node)
200 return;
201 if (m_endNode)
202 QObject::disconnect(this);
203
204 m_endNode = node;
205
206 if (m_endNode) {
207 QObject::connect(m_endNode, &QQuick3DNode::positionChanged, this, &QQuick3DParticleModelBlendParticle::handleEndNodeChanged);
208 QObject::connect(m_endNode, &QQuick3DNode::rotationChanged, this, &QQuick3DParticleModelBlendParticle::handleEndNodeChanged);
209 QObject::connect(m_endNode, &QQuick3DNode::scaleChanged, this, &QQuick3DParticleModelBlendParticle::handleEndNodeChanged);
210 }
211 handleEndNodeChanged();
212 Q_EMIT endNodeChanged();
213}
214
215void QQuick3DParticleModelBlendParticle::setModelBlendMode(ModelBlendMode mode)
216{
217 if (m_modelBlendMode == mode)
218 return;
219 m_modelBlendMode = mode;
220 reset();
221 Q_EMIT modelBlendModeChanged();
222}
223
224void QQuick3DParticleModelBlendParticle::setEndTime(int endTime)
225{
226 if (endTime == m_endTime)
227 return;
228 m_endTime = endTime;
229 Q_EMIT endTimeChanged();
230}
231
232void QQuick3DParticleModelBlendParticle::setActivationNode(QQuick3DNode *activationNode)
233{
234 if (m_activationNode == activationNode)
235 return;
236
237 m_activationNode = activationNode;
238 Q_EMIT activationNodeChanged();
239}
240
241void QQuick3DParticleModelBlendParticle::setEmitMode(ModelBlendEmitMode emitMode)
242{
243 if (m_emitMode == emitMode)
244 return;
245
246 m_emitMode = emitMode;
247 Q_EMIT emitModeChanged();
248}
249
250void QQuick3DParticleModelBlendParticle::regenerate()
251{
252 delete m_model;
253 m_model = nullptr;
254
255 if (!isComponentComplete())
256 return;
257
258 if (!m_delegate)
259 return;
260
261 if (QQuick3DParticleSystem::isGloballyDisabled())
262 return;
263
264 auto *obj = m_delegate->create(m_delegate->creationContext());
265
266 m_model = qobject_cast<QQuick3DModel *>(obj);
267 if (m_model) {
268 updateParticles();
269 auto *psystem = QQuick3DParticle::system();
270 m_model->setParent(psystem);
271 m_model->setParentItem(psystem);
272 } else {
273 delete obj;
274 }
275 handleEndNodeChanged();
276}
277
278static QSSGMesh::Mesh loadModelBlendParticleMesh(const QString &source)
279{
280 QString src = source;
281 if (source.startsWith(QLatin1Char('#'))) {
282 src = QSSGBufferManager::primitivePath(source);
283 src.prepend(QLatin1String(":/"));
284 }
285 src = QDir::cleanPath(src);
286 if (src.startsWith(QLatin1String("qrc:/")))
287 src = src.mid(3);
288
289 QFile file(src);
290 if (!file.open(QFile::ReadOnly))
291 return {};
292 return QSSGMesh::Mesh::loadMesh(&file);
293}
294
295static QVector3D getPosition(const quint8 *srcVertices, quint32 idx, quint32 vertexStride, quint32 posOffset)
296{
297 const quint8 *vertex = srcVertices + idx * vertexStride;
298 return *reinterpret_cast<const QVector3D *>(vertex + posOffset);
299}
300
301static float calcTriangleRadius(const QVector3D &center, const QVector3D &p0, const QVector3D &p1, const QVector3D &p2)
302{
303 return qMax(center.distanceToPoint(p1), qMax(center.distanceToPoint(p2), center.distanceToPoint(p0)));
304}
305
306static void copyToUnindexedVertices(QByteArray &unindexedVertexData,
307 QVector<QVector3D> &centerData,
308 float &maxTriangleRadius,
309 const QByteArray &vertexBufferData,
310 quint32 vertexStride,
311 quint32 posOffset,
312 const QByteArray &indexBufferData,
313 bool u16Indices,
314 quint32 primitiveCount)
315{
316 const quint8 *srcVertices = reinterpret_cast<const quint8 *>(vertexBufferData.data());
317 quint8 *dst = reinterpret_cast<quint8 *>(unindexedVertexData.data());
318 const quint16 *indexData16 = reinterpret_cast<const quint16 *>(indexBufferData.begin());
319 const quint32 *indexData32 = reinterpret_cast<const quint32 *>(indexBufferData.begin());
320 const float c_div3 = 1.0f / 3.0f;
321 for (quint32 i = 0; i < primitiveCount; i++) {
322 quint32 i0, i1, i2;
323 if (u16Indices) {
324 i0 = indexData16[3 * i];
325 i1 = indexData16[3 * i + 1];
326 i2 = indexData16[3 * i + 2];
327 } else {
328 i0 = indexData32[3 * i];
329 i1 = indexData32[3 * i + 1];
330 i2 = indexData32[3 * i + 2];
331 }
332 QVector3D p0 = getPosition(srcVertices, i0, vertexStride, posOffset);
333 QVector3D p1 = getPosition(srcVertices, i1, vertexStride, posOffset);
334 QVector3D p2 = getPosition(srcVertices, i2, vertexStride, posOffset);
335 QVector3D center = (p0 + p1 + p2) * c_div3;
336 centerData[i] = center;
337 maxTriangleRadius = qMax(maxTriangleRadius, calcTriangleRadius(center, p0, p1, p2));
338 memcpy(dst, srcVertices + i0 * vertexStride, vertexStride);
339 dst += vertexStride;
340 memcpy(dst, srcVertices + i1 * vertexStride, vertexStride);
341 dst += vertexStride;
342 memcpy(dst, srcVertices + i2 * vertexStride, vertexStride);
343 dst += vertexStride;
344 }
345}
346
347static void getVertexCenterData(QVector<QVector3D> &centerData,
348 float &maxTriangleRadius,
349 const QByteArray &vertexBufferData,
350 quint32 vertexStride,
351 quint32 posOffset,
352 quint32 primitiveCount)
353{
354 const quint8 *srcVertices = reinterpret_cast<const quint8 *>(vertexBufferData.data());
355 const float c_div3 = 1.0f / 3.0f;
356 for (quint32 i = 0; i < primitiveCount; i++) {
357 QVector3D p0 = getPosition(srcVertices, 3 * i, vertexStride, posOffset);
358 QVector3D p1 = getPosition(srcVertices, 3 * i + 1, vertexStride, posOffset);
359 QVector3D p2 = getPosition(srcVertices, 3 * i + 2, vertexStride, posOffset);
360 QVector3D center = (p0 + p1 + p2) * c_div3;
361 centerData[i] = center;
362 maxTriangleRadius = qMax(maxTriangleRadius, calcTriangleRadius(center, p0, p1, p2));
363 }
364}
365
366void QQuick3DParticleModelBlendParticle::updateParticles()
367{
368 m_maxTriangleRadius = 0.f;
369
370 // The primitives needs to be triangle list without indexing, because each triangle
371 // needs to be it's own primitive and we need vertex index to get the particle index,
372 // which we don't get with indexed primitives
373 if (m_model->geometry()) {
374 QQuick3DGeometry *geometry = m_model->geometry();
375 if (geometry->primitiveType() != QQuick3DGeometry::PrimitiveType::Triangles) {
376 qWarning () << "ModelBlendParticle3D: Invalid geometry primitive type, must be Triangles. ";
377 return;
378 }
379 auto vertexBuffer = geometry->vertexData();
380 auto indexBuffer = geometry->indexData();
381
382 if (!vertexBuffer.size()) {
383 qWarning () << "ModelBlendParticle3D: Invalid geometry, vertexData is empty. ";
384 return;
385 }
386
387 const auto attributeBySemantic = [&](const QQuick3DGeometry *geometry, QQuick3DGeometry::Attribute::Semantic semantic) {
388 for (int i = 0; i < geometry->attributeCount(); i++) {
389 const auto attr = geometry->attribute(i);
390 if (attr.semantic == semantic)
391 return attr;
392 }
393 Q_ASSERT(0);
394 return QQuick3DGeometry::Attribute();
395 };
396
397 if (indexBuffer.size()) {
398 m_modelGeometry = new QQuick3DGeometry;
399
400 m_modelGeometry->setBounds(geometry->boundsMin(), geometry->boundsMax());
401 m_modelGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
402 m_modelGeometry->setStride(geometry->stride());
403
404 for (int i = 0; i < geometry->attributeCount(); i++) {
405 auto attr = geometry->attribute(i);
406 if (attr.semantic != QQuick3DGeometry::Attribute::IndexSemantic)
407 m_modelGeometry->addAttribute(attr);
408 }
409
410 // deindex data
411 QByteArray unindexedVertexData;
412 quint32 primitiveCount = indexBuffer.size();
413 auto indexAttribute = attributeBySemantic(geometry, QQuick3DGeometry::Attribute::IndexSemantic);
414 bool u16IndexType = indexAttribute.componentType == QQuick3DGeometry::Attribute::U16Type;
415 if (u16IndexType)
416 primitiveCount /= 6;
417 else
418 primitiveCount /= 12;
419
420 unindexedVertexData.resize(geometry->stride() * primitiveCount * 3);
421 m_centerData.resize(primitiveCount);
422 m_particleCount = primitiveCount;
423 copyToUnindexedVertices(unindexedVertexData,
424 m_centerData,
425 m_maxTriangleRadius,
426 vertexBuffer,
427 geometry->stride(),
428 attributeBySemantic(geometry, QQuick3DGeometry::Attribute::PositionSemantic).offset,
429 indexBuffer,
430 u16IndexType,
431 primitiveCount);
432
433 m_modelGeometry->setVertexData(unindexedVertexData);
434 m_model->setGeometry(m_modelGeometry);
435 } else {
436 // can use provided geometry directly
437 quint32 primitiveCount = vertexBuffer.size() / geometry->stride() / 3;
438 m_centerData.resize(primitiveCount);
439 m_particleCount = primitiveCount;
440 getVertexCenterData(m_centerData,
441 m_maxTriangleRadius,
442 vertexBuffer,
443 geometry->stride(),
444 attributeBySemantic(geometry, QQuick3DGeometry::Attribute::PositionSemantic).offset,
445 primitiveCount);
446 }
447 } else {
448 const QQmlContext *context = qmlContext(this);
449 QString src = m_model->source().toString();
450 if (context && !src.startsWith(QLatin1Char('#')))
451 src = QQmlFile::urlToLocalFileOrQrc(context->resolvedUrl(m_model->source()));
452 QSSGMesh::Mesh mesh = loadModelBlendParticleMesh(src);
453 if (!mesh.isValid()) {
454 qWarning () << "ModelBlendParticle3D: Unable to load mesh: " << src;
455 return;
456 }
457 if (mesh.drawMode() != QSSGMesh::Mesh::DrawMode::Triangles) {
458 qWarning () << "ModelBlendParticle3D: Invalid mesh primitive type, must be Triangles. ";
459 return;
460 }
461
462 m_modelGeometry = new QQuick3DGeometry;
463
464 const auto vertexBuffer = mesh.vertexBuffer();
465 const auto indexBuffer = mesh.indexBuffer();
466
467 const auto entryOffset = [&](const QSSGMesh::Mesh::VertexBuffer &vb, const QByteArray &name) -> int {
468 for (const auto &e : vb.entries) {
469 if (e.name == name) {
470 Q_ASSERT(e.componentType == QSSGMesh::Mesh::ComponentType::Float32);
471 return e.offset;
472 }
473 }
474 Q_ASSERT(0);
475 return -1;
476 };
477 const auto toAttribute = [&](const QSSGMesh::Mesh::VertexBufferEntry &e) -> QQuick3DGeometry::Attribute {
478 QQuick3DGeometry::Attribute a;
479 a.componentType = QQuick3DGeometryPrivate::toComponentType(e.componentType);
480 a.offset = e.offset;
481 a.semantic = QQuick3DGeometryPrivate::semanticFromName(e.name);
482 return a;
483 };
484
485 const auto indexedPrimitiveCount = [&](const QSSGMesh::Mesh::IndexBuffer &indexBuffer) -> quint32 {
486 if (indexBuffer.componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16)
487 return quint32(indexBuffer.data.size() / sizeof(quint16) / 3);
488 return quint32(indexBuffer.data.size() / sizeof(quint32) / 3);
489 };
490
491 if (indexBuffer.data.size()) {
492 // deindex data
493 QByteArray unindexedVertexData;
494 quint32 primitiveCount = indexedPrimitiveCount(indexBuffer);
495 bool u16IndexType = indexBuffer.componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16;
496 unindexedVertexData.resize(vertexBuffer.stride * primitiveCount * 3);
497 m_centerData.resize(primitiveCount);
498 m_particleCount = primitiveCount;
499
500 copyToUnindexedVertices(unindexedVertexData,
501 m_centerData,
502 m_maxTriangleRadius,
503 vertexBuffer.data,
504 vertexBuffer.stride,
505 entryOffset(vertexBuffer, QByteArray(QSSGMesh::MeshInternal::getPositionAttrName())),
506 indexBuffer.data,
507 u16IndexType,
508 primitiveCount);
509 const auto meshSubsets = mesh.subsets();
510 m_modelGeometry->setBounds(meshSubsets.constFirst().bounds.min, meshSubsets.constFirst().bounds.max);
511 m_modelGeometry->setStride(vertexBuffer.stride);
512 m_modelGeometry->setVertexData(unindexedVertexData);
513 m_modelGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
514 } else {
515 // can use vertexbuffer directly
516 quint32 primitiveCount = vertexBuffer.data.size() / vertexBuffer.stride / 3;
517 m_centerData.resize(primitiveCount);
518 m_particleCount = primitiveCount;
519 getVertexCenterData(m_centerData,
520 m_maxTriangleRadius,
521 vertexBuffer.data,
522 vertexBuffer.stride,
523 entryOffset(vertexBuffer, QByteArray(QSSGMesh::MeshInternal::getPositionAttrName())),
524 primitiveCount);
525 const auto meshSubsets = mesh.subsets();
526 m_modelGeometry->setBounds(meshSubsets.constFirst().bounds.min, meshSubsets.constFirst().bounds.max);
527 m_modelGeometry->setStride(vertexBuffer.stride);
528 m_modelGeometry->setVertexData(vertexBuffer.data);
529 m_modelGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
530 }
531 for (auto &e : vertexBuffer.entries)
532 m_modelGeometry->addAttribute(toAttribute(e));
533 for (auto &s : mesh.subsets())
534 m_modelGeometry->addSubset(s.offset, s.count, s.bounds.min, s.bounds.max, s.name);
535
536 m_model->setSource({});
537 m_model->setGeometry(m_modelGeometry);
538 }
539
540 QMatrix4x4 transform = m_model->sceneTransform();
541 if (m_model->parentNode())
542 transform = m_model->parentNode()->sceneTransform().inverted() * transform;
543 const QVector3D scale = QSSGUtils::mat44::getScale(transform);
544 // Take max component scale for a conservative bounds estimation
545 const float scaleMax = qMax(scale.x(), qMax(scale.y(), scale.z()));
546 m_maxTriangleRadius *= scaleMax;
547
548 m_triangleParticleData.resize(m_particleCount);
549 m_particleData.resize(m_particleCount);
550 m_particleData.fill({});
551 for (int i = 0; i < m_particleCount; i++) {
552 m_triangleParticleData[i].center = m_centerData[i];
553 m_centerData[i] = transform.map(m_centerData[i]);
554 if (m_modelBlendMode == Construct) {
555 m_triangleParticleData[i].size = 0.0f;
556 } else {
557 m_triangleParticleData[i].size = 1.0f;
558 m_triangleParticleData[i].position = m_centerData[i];
559 }
560 }
561 QQuick3DParticle::doSetMaxAmount(m_particleCount);
562}
563
564QSSGRenderGraphObject *QQuick3DParticleModelBlendParticle::updateSpatialNode(QSSGRenderGraphObject *node)
565{
566 if (!m_model)
567 return node;
568 auto *spatialNode = QQuick3DObjectPrivate::get(m_model)->spatialNode;
569 if (!spatialNode) {
570 spatialNode = QQuick3DObjectPrivate::updateSpatialNode(m_model, nullptr);
571 QQuick3DObjectPrivate::get(m_model)->spatialNode = spatialNode;
572 Q_QUICK3D_PROFILE_ASSIGN_ID_SG(this, spatialNode);
573 }
574#if QT_CONFIG(qml_debug)
575 if (m_modelGeometry) {
576 auto *geometrySpatialNode = QQuick3DObjectPrivate::get(m_modelGeometry)->spatialNode;
577 if (geometrySpatialNode)
578 Q_QUICK3D_PROFILE_ASSIGN_ID_SG(this, geometrySpatialNode);
579 }
580#endif
581
582 QSSGRenderModel *model = static_cast<QSSGRenderModel *>(spatialNode);
583
584 if (!model->particleBuffer) {
585 QSSGParticleBuffer *buffer = model->particleBuffer = new QSSGParticleBuffer;
586 buffer->resize(m_particleCount, sizeof(QSSGTriangleParticle));
587 }
588 QQuick3DParticleSystem *psystem = QQuick3DParticle::system();
589 QMatrix4x4 particleMatrix = psystem->sceneTransform().inverted() * m_model->sceneTransform();
590 model->particleMatrix = particleMatrix.inverted();
591 model->hasTransparency = fadeInEffect() == QQuick3DParticle::FadeOpacity || fadeOutEffect() == QQuick3DParticle::FadeOpacity;
592 updateParticleBuffer(model->particleBuffer, psystem->sceneTransform());
593
594 return node;
595}
596
597void QQuick3DParticleModelBlendParticle::componentComplete()
598{
599 if (!system() && qobject_cast<QQuick3DParticleSystem *>(parentItem()))
600 setSystem(qobject_cast<QQuick3DParticleSystem *>(parentItem()));
601
602 // don't call particles componentComplete, we don't wan't to emit maxAmountChanged yet
603 QQuick3DObject::componentComplete();
604 regenerate();
605}
606
607void QQuick3DParticleModelBlendParticle::doSetMaxAmount(int)
608{
609 qWarning() << "ModelBlendParticle3D.maxAmount: Unable to set maximum amount, because it is set from the model.";
610 return;
611}
612
613int QQuick3DParticleModelBlendParticle::nextCurrentIndex(const QQuick3DParticleEmitter *emitter)
614{
615 if (!m_perEmitterData.contains(emitter)) {
616 m_perEmitterData.insert(emitter, PerEmitterData());
617 auto &perEmitter = m_perEmitterData[emitter];
618 perEmitter.emitter = emitter;
619 perEmitter.emitterIndex = m_nextEmitterIndex++;
620 }
621 auto &perEmitter = m_perEmitterData[emitter];
622 int index = QQuick3DParticle::nextCurrentIndex(emitter);
623 if (m_triangleParticleData[index].emitterIndex != perEmitter.emitterIndex) {
624 if (m_triangleParticleData[index].emitterIndex >= 0)
625 perEmitterData(m_triangleParticleData[index].emitterIndex).particleCount--;
626 perEmitter.particleCount++;
627 }
628 m_triangleParticleData[index].emitterIndex = perEmitter.emitterIndex;
629 return index;
630}
631
632
633void QQuick3DParticleModelBlendParticle::setParticleData(int particleIndex,
634 const QVector3D &position,
635 const QVector3D &rotation,
636 const QVector4D &color,
637 float size, float age)
638{
639 auto &dst = m_triangleParticleData[particleIndex];
640 dst = {position, rotation, dst.center, color, age, size, dst.emitterIndex};
641 m_dataChanged = true;
642}
643
644QQuick3DParticleModelBlendParticle::PerEmitterData &QQuick3DParticleModelBlendParticle::perEmitterData(int emitterIndex)
645{
646 for (auto &perEmitter : m_perEmitterData) {
647 if (perEmitter.emitterIndex == emitterIndex)
648 return perEmitter;
649 }
650 return n_noPerEmitterData;
651}
652
653void QQuick3DParticleModelBlendParticle::updateParticleBuffer(QSSGParticleBuffer *buffer, const QMatrix4x4 &sceneTransform)
654{
655 const auto &particles = m_triangleParticleData;
656
657 if (!buffer || !m_dataChanged)
658 return;
659
660 const int particleCount = m_particleCount;
661
662 char *dest = buffer->pointer();
663 const TriangleParticleData *src = particles.data();
664 const int pps = buffer->particlesPerSlice();
665 const int ss = buffer->sliceStride();
666 const int slices = buffer->sliceCount();
667 const float c_degToRad = float(M_PI / 180.0f);
668 int i = 0;
669 QSSGBounds3 bounds;
670 for (int s = 0; s < slices; s++) {
671 QSSGTriangleParticle *dp = reinterpret_cast<QSSGTriangleParticle *>(dest);
672 for (int p = 0; p < pps && i < particleCount; ) {
673 if (src->size > 0.0f)
674 bounds.include(src->position);
675 dp->position = src->position;
676 dp->rotation = src->rotation * c_degToRad;
677 dp->color = src->color;
678 dp->age = src->age;
679 dp->center = src->center;
680 dp->size = src->size;
681 dp++;
682 p++;
683 i++;
684 src++;
685 }
686 dest += ss;
687 }
688
689 bounds.fatten(m_maxTriangleRadius);
690 bounds.transform(sceneTransform);
691 buffer->setBounds(bounds);
692 m_dataChanged = false;
693}
694
695void QQuick3DParticleModelBlendParticle::itemChange(QQuick3DObject::ItemChange change,
696 const QQuick3DObject::ItemChangeData &value)
697{
698 QQuick3DObject::itemChange(change, value);
699 if (change == ItemParentHasChanged && value.sceneManager)
700 regenerate();
701}
702
703void QQuick3DParticleModelBlendParticle::reset()
704{
705 QQuick3DParticle::reset();
706 if (m_particleCount) {
707 for (int i = 0; i < m_particleCount; i++) {
708 if (m_modelBlendMode == Construct) {
709 m_triangleParticleData[i].size = 0.0f;
710 } else {
711 m_triangleParticleData[i].size = 1.0f;
712 m_triangleParticleData[i].position = m_triangleParticleData[i].center;
713 }
714 }
715 }
716}
717
718QVector3D QQuick3DParticleModelBlendParticle::particleCenter(int particleIndex) const
719{
720 return m_centerData[particleIndex];
721}
722
723bool QQuick3DParticleModelBlendParticle::lastParticle() const
724{
725 return m_currentIndex >= m_maxAmount - 1;
726}
727
728static QMatrix3x3 qt_fromEulerRotation(const QVector3D &eulerRotation)
729{
730 float x = qDegreesToRadians(eulerRotation.x());
731 float y = qDegreesToRadians(eulerRotation.y());
732 float z = qDegreesToRadians(eulerRotation.z());
733 float a = cos(x);
734 float b = sin(x);
735 float c = cos(y);
736 float d = sin(y);
737 float e = cos(z);
738 float f = sin(z);
739 QMatrix3x3 ret;
740 float bd = b * d;
741 float ad = a * d;
742 ret(0,0) = c * e;
743 ret(0,1) = -c * f;
744 ret(0,2) = d;
745 ret(1,0) = bd * e + a * f;
746 ret(1,1) = a * e - bd * f;
747 ret(1,2) = -b * c;
748 ret(2,0) = b * f - ad * e;
749 ret(2,1) = ad * f + b * e;
750 ret(2,2) = a * c;
751 return ret;
752}
753
754void QQuick3DParticleModelBlendParticle::handleEndNodeChanged()
755{
756 if (m_endNode && m_model) {
757 if (!m_model->rotation().isIdentity()) {
758 // Use the same function as the shader for end node rotation so that they produce same matrix
759 QMatrix3x3 r1 = qt_fromEulerRotation(m_endNode->eulerRotation());
760 QMatrix3x3 r2 = m_model->rotation().toRotationMatrix();
761 QMatrix3x3 r = r2 * r1.transposed() * r2.transposed();
762 m_endNodeRotation = m_endNode->eulerRotation();
763 m_endRotationMatrix = QMatrix4x4(r);
764 } else {
765 m_endNodeRotation = m_endNode->eulerRotation();
766 m_endRotationMatrix = QMatrix4x4(m_endNode->rotation().toRotationMatrix().transposed());
767 }
768 m_endNodePosition = m_endNode->position();
769 m_endNodeScale = m_endNode->scale();
770 } else {
771 m_endNodePosition = QVector3D();
772 m_endNodeRotation = QVector3D();
773 m_endNodeScale = QVector3D(1.0f, 1.0f, 1.0f);
774 m_endRotationMatrix.setToIdentity();
775 }
776}
777
778QVector3D QQuick3DParticleModelBlendParticle::particleEndPosition(int idx) const
779{
780 return m_endRotationMatrix.map(QVector3D(m_endNodeScale * m_centerData[idx])) + m_endNodePosition;
781}
782
783QVector3D QQuick3DParticleModelBlendParticle::particleEndRotation(int) const
784{
785 return m_endNodeRotation;
786}
787
788int QQuick3DParticleModelBlendParticle::randomIndex(int particleIndex)
789{
790 if (m_randomParticles.isEmpty()) {
791 m_randomParticles.resize(m_maxAmount);
792 for (int i = 0; i < m_maxAmount; i++)
793 m_randomParticles[i] = i;
794
795 // Randomize particle indices just once
796 QRandomGenerator rand(system()->rand()->generator());
797 for (int i = 0; i < m_maxAmount; i++) {
798 int ridx = rand.generate() % m_maxAmount;
799 if (i != ridx)
800 qSwap(m_randomParticles[i], m_randomParticles[ridx]);
801 }
802 }
803 return m_randomParticles[particleIndex];
804}
805
806QT_END_NAMESPACE
Combined button and popup list for selecting options.
static float calcTriangleRadius(const QVector3D &center, 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 > &centerData, float &maxTriangleRadius, const QByteArray &vertexBufferData, quint32 vertexStride, quint32 posOffset, quint32 primitiveCount)
static void copyToUnindexedVertices(QByteArray &unindexedVertexData, QVector< QVector3D > &centerData, 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)