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 m_modelGeometry->setBounds(mesh.subsets().first().bounds.min, mesh.subsets().first().bounds.max);
510 m_modelGeometry->setStride(vertexBuffer.stride);
511 m_modelGeometry->setVertexData(unindexedVertexData);
512 m_modelGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
513 } else {
514 // can use vertexbuffer directly
515 quint32 primitiveCount = vertexBuffer.data.size() / vertexBuffer.stride / 3;
516 m_centerData.resize(primitiveCount);
517 m_particleCount = primitiveCount;
518 getVertexCenterData(m_centerData,
519 m_maxTriangleRadius,
520 vertexBuffer.data,
521 vertexBuffer.stride,
522 entryOffset(vertexBuffer, QByteArray(QSSGMesh::MeshInternal::getPositionAttrName())),
523 primitiveCount);
524 m_modelGeometry->setBounds(mesh.subsets().first().bounds.min, mesh.subsets().first().bounds.max);
525 m_modelGeometry->setStride(vertexBuffer.stride);
526 m_modelGeometry->setVertexData(vertexBuffer.data);
527 m_modelGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
528 }
529 for (auto &e : vertexBuffer.entries)
530 m_modelGeometry->addAttribute(toAttribute(e));
531 for (auto &s : mesh.subsets())
532 m_modelGeometry->addSubset(s.offset, s.count, s.bounds.min, s.bounds.max, s.name);
533
534 m_model->setSource({});
535 m_model->setGeometry(m_modelGeometry);
536 }
537
538 QMatrix4x4 transform = m_model->sceneTransform();
539 if (m_model->parentNode())
540 transform = m_model->parentNode()->sceneTransform().inverted() * transform;
541 const QVector3D scale = QSSGUtils::mat44::getScale(transform);
542 // Take max component scale for a conservative bounds estimation
543 const float scaleMax = qMax(scale.x(), qMax(scale.y(), scale.z()));
544 m_maxTriangleRadius *= scaleMax;
545
546 m_triangleParticleData.resize(m_particleCount);
547 m_particleData.resize(m_particleCount);
548 m_particleData.fill({});
549 for (int i = 0; i < m_particleCount; i++) {
550 m_triangleParticleData[i].center = m_centerData[i];
551 m_centerData[i] = transform.map(m_centerData[i]);
552 if (m_modelBlendMode == Construct) {
553 m_triangleParticleData[i].size = 0.0f;
554 } else {
555 m_triangleParticleData[i].size = 1.0f;
556 m_triangleParticleData[i].position = m_centerData[i];
557 }
558 }
559 QQuick3DParticle::doSetMaxAmount(m_particleCount);
560}
561
562QSSGRenderGraphObject *QQuick3DParticleModelBlendParticle::updateSpatialNode(QSSGRenderGraphObject *node)
563{
564 if (!m_model)
565 return node;
566 auto *spatialNode = QQuick3DObjectPrivate::get(m_model)->spatialNode;
567 if (!spatialNode) {
568 spatialNode = QQuick3DObjectPrivate::updateSpatialNode(m_model, nullptr);
569 QQuick3DObjectPrivate::get(m_model)->spatialNode = spatialNode;
570 Q_QUICK3D_PROFILE_ASSIGN_ID_SG(this, spatialNode);
571 }
572#if QT_CONFIG(qml_debug)
573 if (m_modelGeometry) {
574 auto *geometrySpatialNode = QQuick3DObjectPrivate::get(m_modelGeometry)->spatialNode;
575 if (geometrySpatialNode)
576 Q_QUICK3D_PROFILE_ASSIGN_ID_SG(this, geometrySpatialNode);
577 }
578#endif
579
580 QSSGRenderModel *model = static_cast<QSSGRenderModel *>(spatialNode);
581
582 if (!model->particleBuffer) {
583 QSSGParticleBuffer *buffer = model->particleBuffer = new QSSGParticleBuffer;
584 buffer->resize(m_particleCount, sizeof(QSSGTriangleParticle));
585 }
586 QQuick3DParticleSystem *psystem = QQuick3DParticle::system();
587 QMatrix4x4 particleMatrix = psystem->sceneTransform().inverted() * m_model->sceneTransform();
588 model->particleMatrix = particleMatrix.inverted();
589 model->hasTransparency = fadeInEffect() == QQuick3DParticle::FadeOpacity || fadeOutEffect() == QQuick3DParticle::FadeOpacity;
590 updateParticleBuffer(model->particleBuffer, psystem->sceneTransform());
591
592 return node;
593}
594
595void QQuick3DParticleModelBlendParticle::componentComplete()
596{
597 if (!system() && qobject_cast<QQuick3DParticleSystem *>(parentItem()))
598 setSystem(qobject_cast<QQuick3DParticleSystem *>(parentItem()));
599
600 // don't call particles componentComplete, we don't wan't to emit maxAmountChanged yet
601 QQuick3DObject::componentComplete();
602 regenerate();
603}
604
605void QQuick3DParticleModelBlendParticle::doSetMaxAmount(int)
606{
607 qWarning() << "ModelBlendParticle3D.maxAmount: Unable to set maximum amount, because it is set from the model.";
608 return;
609}
610
611int QQuick3DParticleModelBlendParticle::nextCurrentIndex(const QQuick3DParticleEmitter *emitter)
612{
613 if (!m_perEmitterData.contains(emitter)) {
614 m_perEmitterData.insert(emitter, PerEmitterData());
615 auto &perEmitter = m_perEmitterData[emitter];
616 perEmitter.emitter = emitter;
617 perEmitter.emitterIndex = m_nextEmitterIndex++;
618 }
619 auto &perEmitter = m_perEmitterData[emitter];
620 int index = QQuick3DParticle::nextCurrentIndex(emitter);
621 if (m_triangleParticleData[index].emitterIndex != perEmitter.emitterIndex) {
622 if (m_triangleParticleData[index].emitterIndex >= 0)
623 perEmitterData(m_triangleParticleData[index].emitterIndex).particleCount--;
624 perEmitter.particleCount++;
625 }
626 m_triangleParticleData[index].emitterIndex = perEmitter.emitterIndex;
627 return index;
628}
629
630
631void QQuick3DParticleModelBlendParticle::setParticleData(int particleIndex,
632 const QVector3D &position,
633 const QVector3D &rotation,
634 const QVector4D &color,
635 float size, float age)
636{
637 auto &dst = m_triangleParticleData[particleIndex];
638 dst = {position, rotation, dst.center, color, age, size, dst.emitterIndex};
639 m_dataChanged = true;
640}
641
642QQuick3DParticleModelBlendParticle::PerEmitterData &QQuick3DParticleModelBlendParticle::perEmitterData(int emitterIndex)
643{
644 for (auto &perEmitter : m_perEmitterData) {
645 if (perEmitter.emitterIndex == emitterIndex)
646 return perEmitter;
647 }
648 return n_noPerEmitterData;
649}
650
651void QQuick3DParticleModelBlendParticle::updateParticleBuffer(QSSGParticleBuffer *buffer, const QMatrix4x4 &sceneTransform)
652{
653 const auto &particles = m_triangleParticleData;
654
655 if (!buffer || !m_dataChanged)
656 return;
657
658 const int particleCount = m_particleCount;
659
660 char *dest = buffer->pointer();
661 const TriangleParticleData *src = particles.data();
662 const int pps = buffer->particlesPerSlice();
663 const int ss = buffer->sliceStride();
664 const int slices = buffer->sliceCount();
665 const float c_degToRad = float(M_PI / 180.0f);
666 int i = 0;
667 QSSGBounds3 bounds;
668 for (int s = 0; s < slices; s++) {
669 QSSGTriangleParticle *dp = reinterpret_cast<QSSGTriangleParticle *>(dest);
670 for (int p = 0; p < pps && i < particleCount; ) {
671 if (src->size > 0.0f)
672 bounds.include(src->position);
673 dp->position = src->position;
674 dp->rotation = src->rotation * c_degToRad;
675 dp->color = src->color;
676 dp->age = src->age;
677 dp->center = src->center;
678 dp->size = src->size;
679 dp++;
680 p++;
681 i++;
682 src++;
683 }
684 dest += ss;
685 }
686
687 bounds.fatten(m_maxTriangleRadius);
688 bounds.transform(sceneTransform);
689 buffer->setBounds(bounds);
690 m_dataChanged = false;
691}
692
693void QQuick3DParticleModelBlendParticle::itemChange(QQuick3DObject::ItemChange change,
694 const QQuick3DObject::ItemChangeData &value)
695{
696 QQuick3DObject::itemChange(change, value);
697 if (change == ItemParentHasChanged && value.sceneManager)
698 regenerate();
699}
700
701void QQuick3DParticleModelBlendParticle::reset()
702{
703 QQuick3DParticle::reset();
704 if (m_particleCount) {
705 for (int i = 0; i < m_particleCount; i++) {
706 if (m_modelBlendMode == Construct) {
707 m_triangleParticleData[i].size = 0.0f;
708 } else {
709 m_triangleParticleData[i].size = 1.0f;
710 m_triangleParticleData[i].position = m_triangleParticleData[i].center;
711 }
712 }
713 }
714}
715
716QVector3D QQuick3DParticleModelBlendParticle::particleCenter(int particleIndex) const
717{
718 return m_centerData[particleIndex];
719}
720
721bool QQuick3DParticleModelBlendParticle::lastParticle() const
722{
723 return m_currentIndex >= m_maxAmount - 1;
724}
725
726static QMatrix3x3 qt_fromEulerRotation(const QVector3D &eulerRotation)
727{
728 float x = qDegreesToRadians(eulerRotation.x());
729 float y = qDegreesToRadians(eulerRotation.y());
730 float z = qDegreesToRadians(eulerRotation.z());
731 float a = cos(x);
732 float b = sin(x);
733 float c = cos(y);
734 float d = sin(y);
735 float e = cos(z);
736 float f = sin(z);
737 QMatrix3x3 ret;
738 float bd = b * d;
739 float ad = a * d;
740 ret(0,0) = c * e;
741 ret(0,1) = -c * f;
742 ret(0,2) = d;
743 ret(1,0) = bd * e + a * f;
744 ret(1,1) = a * e - bd * f;
745 ret(1,2) = -b * c;
746 ret(2,0) = b * f - ad * e;
747 ret(2,1) = ad * f + b * e;
748 ret(2,2) = a * c;
749 return ret;
750}
751
752void QQuick3DParticleModelBlendParticle::handleEndNodeChanged()
753{
754 if (m_endNode && m_model) {
755 if (!m_model->rotation().isIdentity()) {
756 // Use the same function as the shader for end node rotation so that they produce same matrix
757 QMatrix3x3 r1 = qt_fromEulerRotation(m_endNode->eulerRotation());
758 QMatrix3x3 r2 = m_model->rotation().toRotationMatrix();
759 QMatrix3x3 r = r2 * r1.transposed() * r2.transposed();
760 m_endNodeRotation = m_endNode->eulerRotation();
761 m_endRotationMatrix = QMatrix4x4(r);
762 } else {
763 m_endNodeRotation = m_endNode->eulerRotation();
764 m_endRotationMatrix = QMatrix4x4(m_endNode->rotation().toRotationMatrix().transposed());
765 }
766 m_endNodePosition = m_endNode->position();
767 m_endNodeScale = m_endNode->scale();
768 } else {
769 m_endNodePosition = QVector3D();
770 m_endNodeRotation = QVector3D();
771 m_endNodeScale = QVector3D(1.0f, 1.0f, 1.0f);
772 m_endRotationMatrix.setToIdentity();
773 }
774}
775
776QVector3D QQuick3DParticleModelBlendParticle::particleEndPosition(int idx) const
777{
778 return m_endRotationMatrix.map(QVector3D(m_endNodeScale * m_centerData[idx])) + m_endNodePosition;
779}
780
781QVector3D QQuick3DParticleModelBlendParticle::particleEndRotation(int) const
782{
783 return m_endNodeRotation;
784}
785
786int QQuick3DParticleModelBlendParticle::randomIndex(int particleIndex)
787{
788 if (m_randomParticles.isEmpty()) {
789 m_randomParticles.resize(m_maxAmount);
790 for (int i = 0; i < m_maxAmount; i++)
791 m_randomParticles[i] = i;
792
793 // Randomize particle indices just once
794 QRandomGenerator rand(system()->rand()->generator());
795 for (int i = 0; i < m_maxAmount; i++) {
796 int ridx = rand.generate() % m_maxAmount;
797 if (i != ridx)
798 qSwap(m_randomParticles[i], m_randomParticles[ridx]);
799 }
800 }
801 return m_randomParticles[particleIndex];
802}
803
804QT_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)