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

source code of qtquick3d/src/quick3dparticles/qquick3dparticlemodelblendparticle.cpp