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#if QT_CONFIG(qml_debug)
571 if (m_modelGeometry) {
572 auto *geometrySpatialNode = QQuick3DObjectPrivate::get(item: m_modelGeometry)->spatialNode;
573 if (geometrySpatialNode)
574 Q_QUICK3D_PROFILE_ASSIGN_ID_SG(this, geometrySpatialNode);
575 }
576#endif
577
578 QSSGRenderModel *model = static_cast<QSSGRenderModel *>(spatialNode);
579
580 if (!model->particleBuffer) {
581 QSSGParticleBuffer *buffer = model->particleBuffer = new QSSGParticleBuffer;
582 buffer->resize(particleCount: m_particleCount, particleSize: sizeof(QSSGTriangleParticle));
583 }
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(buffer: model->particleBuffer, sceneTransform: psystem->sceneTransform());
589
590 return node;
591}
592
593void QQuick3DParticleModelBlendParticle::componentComplete()
594{
595 if (!system() && qobject_cast<QQuick3DParticleSystem *>(object: parentItem()))
596 setSystem(qobject_cast<QQuick3DParticleSystem *>(object: parentItem()));
597
598 // don't call particles componentComplete, we don't wan't to emit maxAmountChanged yet
599 QQuick3DObject::componentComplete();
600 regenerate();
601}
602
603void QQuick3DParticleModelBlendParticle::doSetMaxAmount(int)
604{
605 qWarning() << "ModelBlendParticle3D.maxAmount: Unable to set maximum amount, because it is set from the model.";
606 return;
607}
608
609int QQuick3DParticleModelBlendParticle::nextCurrentIndex(const QQuick3DParticleEmitter *emitter)
610{
611 if (!m_perEmitterData.contains(key: emitter)) {
612 m_perEmitterData.insert(key: emitter, value: PerEmitterData());
613 auto &perEmitter = m_perEmitterData[emitter];
614 perEmitter.emitter = emitter;
615 perEmitter.emitterIndex = m_nextEmitterIndex++;
616 }
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(emitterIndex: m_triangleParticleData[index].emitterIndex).particleCount--;
622 perEmitter.particleCount++;
623 }
624 m_triangleParticleData[index].emitterIndex = perEmitter.emitterIndex;
625 return index;
626}
627
628
629void QQuick3DParticleModelBlendParticle::setParticleData(int particleIndex,
630 const QVector3D &position,
631 const QVector3D &rotation,
632 const QVector4D &color,
633 float size, float age)
634{
635 auto &dst = m_triangleParticleData[particleIndex];
636 dst = {.position: position, .rotation: rotation, .center: dst.center, .color: color, .age: age, .size: size, .emitterIndex: dst.emitterIndex};
637 m_dataChanged = true;
638}
639
640QQuick3DParticleModelBlendParticle::PerEmitterData &QQuick3DParticleModelBlendParticle::perEmitterData(int emitterIndex)
641{
642 for (auto &perEmitter : m_perEmitterData) {
643 if (perEmitter.emitterIndex == emitterIndex)
644 return perEmitter;
645 }
646 return n_noPerEmitterData;
647}
648
649void QQuick3DParticleModelBlendParticle::updateParticleBuffer(QSSGParticleBuffer *buffer, const QMatrix4x4 &sceneTransform)
650{
651 const auto &particles = m_triangleParticleData;
652
653 if (!buffer || !m_dataChanged)
654 return;
655
656 const int particleCount = m_particleCount;
657
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);
664 int i = 0;
665 QSSGBounds3 bounds;
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(v: src->position);
671 dp->position = src->position;
672 dp->rotation = src->rotation * c_degToRad;
673 dp->color = src->color;
674 dp->age = src->age;
675 dp->center = src->center;
676 dp->size = src->size;
677 dp++;
678 p++;
679 i++;
680 src++;
681 }
682 dest += ss;
683 }
684
685 bounds.fatten(distance: m_maxTriangleRadius);
686 bounds.transform(inMatrix: sceneTransform);
687 buffer->setBounds(bounds);
688 m_dataChanged = false;
689}
690
691void QQuick3DParticleModelBlendParticle::itemChange(QQuick3DObject::ItemChange change,
692 const QQuick3DObject::ItemChangeData &value)
693{
694 QQuick3DObject::itemChange(change, value);
695 if (change == ItemParentHasChanged && value.sceneManager)
696 regenerate();
697}
698
699void QQuick3DParticleModelBlendParticle::reset()
700{
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;
706 } else {
707 m_triangleParticleData[i].size = 1.0f;
708 m_triangleParticleData[i].position = m_triangleParticleData[i].center;
709 }
710 }
711 }
712}
713
714QVector3D QQuick3DParticleModelBlendParticle::particleCenter(int particleIndex) const
715{
716 return m_centerData[particleIndex];
717}
718
719bool QQuick3DParticleModelBlendParticle::lastParticle() const
720{
721 return m_currentIndex >= m_maxAmount - 1;
722}
723
724static QMatrix3x3 qt_fromEulerRotation(const QVector3D &eulerRotation)
725{
726 float x = qDegreesToRadians(degrees: eulerRotation.x());
727 float y = qDegreesToRadians(degrees: eulerRotation.y());
728 float z = qDegreesToRadians(degrees: eulerRotation.z());
729 float a = cos(x: x);
730 float b = sin(x: x);
731 float c = cos(x: y);
732 float d = sin(x: y);
733 float e = cos(x: z);
734 float f = sin(x: z);
735 QMatrix3x3 ret;
736 float bd = b * d;
737 float ad = a * d;
738 ret(0,0) = c * e;
739 ret(0,1) = -c * f;
740 ret(0,2) = d;
741 ret(1,0) = bd * e + a * f;
742 ret(1,1) = a * e - bd * f;
743 ret(1,2) = -b * c;
744 ret(2,0) = b * f - ad * e;
745 ret(2,1) = ad * f + b * e;
746 ret(2,2) = a * c;
747 return ret;
748}
749
750void QQuick3DParticleModelBlendParticle::handleEndNodeChanged()
751{
752 if (m_endNode && m_model) {
753 if (!m_model->rotation().isIdentity()) {
754 // Use the same function as the shader for end node rotation so that they produce same matrix
755 QMatrix3x3 r1 = qt_fromEulerRotation(eulerRotation: 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);
760 } else {
761 m_endNodeRotation = m_endNode->eulerRotation();
762 m_endRotationMatrix = QMatrix4x4(m_endNode->rotation().toRotationMatrix().transposed());
763 }
764 m_endNodePosition = m_endNode->position();
765 m_endNodeScale = m_endNode->scale();
766 } else {
767 m_endNodePosition = QVector3D();
768 m_endNodeRotation = QVector3D();
769 m_endNodeScale = QVector3D(1.0f, 1.0f, 1.0f);
770 m_endRotationMatrix.setToIdentity();
771 }
772}
773
774QVector3D QQuick3DParticleModelBlendParticle::particleEndPosition(int idx) const
775{
776 return m_endRotationMatrix.map(point: QVector3D(m_endNodeScale * m_centerData[idx])) + m_endNodePosition;
777}
778
779QVector3D QQuick3DParticleModelBlendParticle::particleEndRotation(int) const
780{
781 return m_endNodeRotation;
782}
783
784int QQuick3DParticleModelBlendParticle::randomIndex(int particleIndex)
785{
786 if (m_randomParticles.isEmpty()) {
787 m_randomParticles.resize(size: m_maxAmount);
788 for (int i = 0; i < m_maxAmount; i++)
789 m_randomParticles[i] = i;
790
791 // Randomize particle indices just once
792 QRandomGenerator rand(system()->rand()->generator());
793 for (int i = 0; i < m_maxAmount; i++) {
794 int ridx = rand.generate() % m_maxAmount;
795 if (i != ridx)
796 qSwap(value1&: m_randomParticles[i], value2&: m_randomParticles[ridx]);
797 }
798 }
799 return m_randomParticles[particleIndex];
800}
801
802QT_END_NAMESPACE
803

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