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