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
qquick3dparticlespriteparticle.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
8
9#include <QtQuick3D/private/qquick3dobject_p.h>
10
11#include <QtQuick3DUtils/private/qssgutils_p.h>
12
14
15/*!
16 \qmltype SpriteParticle3D
17 \inherits Particle3D
18 \inqmlmodule QtQuick3D.Particles3D
19 \brief Particle using a 2D sprite texture.
20 \since 6.2
21
22 The SpriteParticle3D is a logical particle element that creates particles
23 from a 2D sprite texture.
24*/
25
26QQuick3DParticleSpriteParticle::QQuick3DParticleSpriteParticle(QQuick3DNode *parent)
27 : QQuick3DParticle(parent)
28{
29 m_connections.insert("maxAmount", QObject::connect(this, &QQuick3DParticle::maxAmountChanged, this, [this]() {
30 handleMaxAmountChanged(m_maxAmount);
31 }));
32 m_connections.insert("system", QObject::connect(this, &QQuick3DParticle::systemChanged, this, [this]() {
33 handleSystemChanged(system());
34 }));
35 m_connections.insert("sortMode", QObject::connect(this, &QQuick3DParticle::sortModeChanged, this, [this]() {
36 markNodesDirty();
37 }));
38}
39
40QQuick3DParticleSpriteParticle::~QQuick3DParticleSpriteParticle()
41{
42 if (m_spriteSequence)
43 m_spriteSequence->m_parentParticle = nullptr;
44 for (const auto &connection : std::as_const(m_connections))
45 QObject::disconnect(connection);
46 deleteNodes();
47
48 auto lightList = lights();
49 qmlClearLights(&lightList);
50}
51
52void QQuick3DParticleSpriteParticle::deleteNodes()
53{
54 for (const PerEmitterData &value : std::as_const(m_perEmitterData)) {
55 value.particleUpdateNode->m_particle = nullptr;
56 delete value.particleUpdateNode;
57 }
58 m_perEmitterData.clear();
59}
60
61/*!
62 \qmlproperty enumeration SpriteParticle3D::BlendMode
63
64 Defines the blending mode for the particles.
65
66 \value SpriteParticle3D.SourceOver
67 Blend particles with SourceOver mode.
68 \value SpriteParticle3D.Screen
69 Blend particles with Screen mode.
70 \value SpriteParticle3D.Multiply
71 Blend particles with Multiply mode.
72*/
73
74/*!
75 \qmlproperty BlendMode SpriteParticle3D::blendMode
76
77 This property defines the blending mode used for rendering the particles.
78
79 The default value is \c SpriteParticle3D.SourceOver.
80*/
81QQuick3DParticleSpriteParticle::BlendMode QQuick3DParticleSpriteParticle::blendMode() const
82{
83 return m_blendMode;
84}
85
86/*!
87 \qmlproperty Texture SpriteParticle3D::sprite
88
89 This property defines the \l Texture used for the particles.
90
91 For example, to use "snowFlake.png" as the particles texture:
92
93 \qml
94 SpriteParticle3D {
95 id: snowParticle
96 ...
97 sprite: Texture {
98 source: "images/snowflake.png"
99 }
100 }
101 \endqml
102*/
103QQuick3DTexture *QQuick3DParticleSpriteParticle::sprite() const
104{
105 return m_sprite;
106}
107
108/*!
109 \qmlproperty SpriteSequence3D SpriteParticle3D::spriteSequence
110
111 This property defines the sprite sequence properties for the particle.
112 If the \l sprite texture contains a frame sequence, set this property
113 to define the frame count, animation direction etc. features.
114*/
115
116QQuick3DParticleSpriteSequence *QQuick3DParticleSpriteParticle::spriteSequence() const
117{
118 return m_spriteSequence;
119}
120
121/*!
122 \qmlproperty bool SpriteParticle3D::billboard
123
124 This property defines if the particle texture should always be aligned
125 face towards the screen.
126
127 \note When set to \c true, \l Particle3D \l {Particle3D::alignMode}{alignMode}
128 property does not have an effect.
129
130 The default value is \c false.
131*/
132bool QQuick3DParticleSpriteParticle::billboard() const
133{
134 return m_billboard;
135}
136
137/*!
138 \qmlproperty real SpriteParticle3D::particleScale
139
140 This property defines the scale multiplier of the particles.
141 To adjust the particles sizes in the emitter, use \ ParticleEmitter3D
142 \l {ParticleEmitter3D::particleScale}{particleScale},
143 \l {ParticleEmitter3D::particleEndScale}{particleEndScale}, and
144 \l {ParticleEmitter3D::particleScaleVariation}{particleScaleVariation}
145 properties.
146
147 The default value is \c 5.0.
148*/
149float QQuick3DParticleSpriteParticle::particleScale() const
150{
151 return m_particleScale;
152}
153
154/*!
155 \qmlproperty Texture SpriteParticle3D::colorTable
156
157 This property defines the \l Texture used for coloring the particles.
158 The image can be a 1D or a 2D texture. Horizontal pixels determine the particle color over its
159 \l {ParticleEmitter3D::lifeSpan}{lifeSpan}. For example, when the particle is halfway through
160 its life, it will have the color specified halfway across the image. If the image is 2D,
161 vertical row is randomly selected for each particle. For example, a c {256 x 4} image
162 contains \c 4 different coloring options for particles.
163*/
164QQuick3DTexture *QQuick3DParticleSpriteParticle::colorTable() const
165{
166 return m_colorTable;
167}
168
169/*!
170 \qmlproperty list<Light> SpriteParticle3D::lights
171 \since 6.3
172
173 This property contains a list of \l [QtQuick3D QML] {Light}{lights} used
174 for rendering the particles.
175 \note For optimal performance, define lights only if they are needed and keep
176 the amount of lights at minimum.
177*/
178
179QQmlListProperty<QQuick3DAbstractLight> QQuick3DParticleSpriteParticle::lights()
180{
181 return QQmlListProperty<QQuick3DAbstractLight>(this,
182 nullptr,
183 QQuick3DParticleSpriteParticle::qmlAppendLight,
184 QQuick3DParticleSpriteParticle::qmlLightsCount,
185 QQuick3DParticleSpriteParticle::qmlLightAt,
186 QQuick3DParticleSpriteParticle::qmlClearLights);
187}
188
189/*!
190 \qmlproperty real SpriteParticle3D::offsetX
191 \since 6.3
192
193 This property defines the particles offset in the X axis
194*/
195float QQuick3DParticleSpriteParticle::offsetX() const
196{
197 return m_offset.x();
198}
199
200/*!
201 \qmlproperty real SpriteParticle3D::offsetY
202 \since 6.3
203
204 This property defines the particles offset in the Y axis
205*/
206float QQuick3DParticleSpriteParticle::offsetY() const
207{
208 return m_offset.y();
209}
210
211/*!
212 \qmlproperty bool SpriteParticle3D::castsReflections
213 \since 6.4
214
215 When this property is set to \c true, the sprite is rendered by reflection probes and can be
216 seen in the reflections.
217*/
218bool QQuick3DParticleSpriteParticle::castsReflections() const
219{
220 return m_castsReflections;
221}
222
223void QQuick3DParticleSpriteParticle::setBlendMode(BlendMode blendMode)
224{
225 if (m_blendMode == blendMode)
226 return;
227 m_blendMode = blendMode;
228 markNodesDirty();
229 Q_EMIT blendModeChanged();
230}
231
232void QQuick3DParticleSpriteParticle::setSprite(QQuick3DTexture *sprite)
233{
234 if (m_sprite == sprite)
235 return;
236
237 QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DParticleSpriteParticle::setSprite, sprite, m_sprite);
238
239 m_sprite = sprite;
240 markNodesDirty();
241 Q_EMIT spriteChanged();
242}
243
244void QQuick3DParticleSpriteParticle::setSpriteSequence(QQuick3DParticleSpriteSequence *spriteSequence)
245{
246 if (m_spriteSequence == spriteSequence)
247 return;
248
249 m_spriteSequence = spriteSequence;
250 updateFeatureLevel();
251 markNodesDirty();
252 Q_EMIT spriteSequenceChanged();
253}
254
255void QQuick3DParticleSpriteParticle::setBillboard(bool billboard)
256{
257 if (m_billboard == billboard)
258 return;
259 m_billboard = billboard;
260 markNodesDirty();
261 Q_EMIT billboardChanged();
262}
263
264void QQuick3DParticleSpriteParticle::setParticleScale(float scale)
265{
266 if (qFuzzyCompare(scale, m_particleScale))
267 return;
268 m_particleScale = scale;
269 markNodesDirty();
270 Q_EMIT particleScaleChanged();
271}
272
273void QQuick3DParticleSpriteParticle::setColorTable(QQuick3DTexture *colorTable)
274{
275 if (m_colorTable == colorTable)
276 return;
277
278 QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DParticleSpriteParticle::setColorTable, colorTable, m_colorTable);
279
280 m_colorTable = colorTable;
281 updateFeatureLevel();
282 markNodesDirty();
283 Q_EMIT colorTableChanged();
284}
285
286void QQuick3DParticleSpriteParticle::setOffsetX(float value)
287{
288 if (qFuzzyCompare(value, m_offset.x()))
289 return;
290
291 m_offset.setX(value);
292 emit offsetXChanged();
293}
294
295void QQuick3DParticleSpriteParticle::setOffsetY(float value)
296{
297 if (qFuzzyCompare(value, m_offset.y()))
298 return;
299
300 m_offset.setY(value);
301 emit offsetYChanged();
302}
303
304void QQuick3DParticleSpriteParticle::setCastsReflections(bool castsReflections)
305{
306 if (m_castsReflections == castsReflections)
307 return;
308 m_castsReflections = castsReflections;
309 emit castsReflectionsChanged();
310}
311
312void QQuick3DParticleSpriteParticle::itemChange(QQuick3DObject::ItemChange change,
313 const QQuick3DObject::ItemChangeData &value)
314{
315 if (change == QQuick3DObject::ItemSceneChange)
316 updateSceneManager(value.sceneManager);
317}
318
319static QSSGRenderParticles::BlendMode mapBlendMode(QQuick3DParticleSpriteParticle::BlendMode mode)
320{
321 switch (mode) {
322 case QQuick3DParticleSpriteParticle::SourceOver:
323 return QSSGRenderParticles::BlendMode::SourceOver;
324 case QQuick3DParticleSpriteParticle::Screen:
325 return QSSGRenderParticles::BlendMode::Screen;
326 case QQuick3DParticleSpriteParticle::Multiply:
327 return QSSGRenderParticles::BlendMode::Multiply;
328 }
329
330 Q_UNREACHABLE_RETURN(QSSGRenderParticles::BlendMode::SourceOver);
331}
332
333QSSGRenderParticles::FeatureLevel QQuick3DParticleSpriteParticle::mapFeatureLevel(QQuick3DParticleSpriteParticle::FeatureLevel level)
334{
335 switch (level) {
336 case QQuick3DParticleSpriteParticle::Simple:
337 return QSSGRenderParticles::FeatureLevel::Simple;
338 case QQuick3DParticleSpriteParticle::Mapped:
339 return QSSGRenderParticles::FeatureLevel::Mapped;
340 case QQuick3DParticleSpriteParticle::Animated:
341 return QSSGRenderParticles::FeatureLevel::Animated;
342 case QQuick3DParticleSpriteParticle::SimpleVLight:
343 return QSSGRenderParticles::FeatureLevel::SimpleVLight;
344 case QQuick3DParticleSpriteParticle::MappedVLight:
345 return QSSGRenderParticles::FeatureLevel::MappedVLight;
346 case QQuick3DParticleSpriteParticle::AnimatedVLight:
347 return QSSGRenderParticles::FeatureLevel::AnimatedVLight;
348 }
349
350 Q_UNREACHABLE_RETURN(QSSGRenderParticles::FeatureLevel::Simple);
351}
352
353QSSGRenderGraphObject *QQuick3DParticleSpriteParticle::ParticleUpdateNode::updateSpatialNode(QSSGRenderGraphObject *node)
354{
355 if (m_particle) {
356 node = m_particle->updateParticleNode(this, node);
357 QQuick3DNode::updateSpatialNode(node);
358 Q_QUICK3D_PROFILE_ASSIGN_ID_SG(m_particle, node);
359 auto particles = static_cast<QSSGRenderParticles *>(node);
360
361 if (m_particle->m_featureLevel == QQuick3DParticleSpriteParticle::Animated || m_particle->m_featureLevel == QQuick3DParticleSpriteParticle::AnimatedVLight)
362 m_particle->updateAnimatedParticleBuffer(this, particles);
363 else
364 m_particle->updateParticleBuffer(this, particles);
365
366 m_nodeDirty = false;
367 }
368 return node;
369}
370
371QQuick3DParticleSpriteParticle::PerEmitterData &QQuick3DParticleSpriteParticle::perEmitterData(const QQuick3DNode *updateNode)
372{
373 for (auto &perEmitter : m_perEmitterData) {
374 if (perEmitter.particleUpdateNode == updateNode)
375 return perEmitter;
376 }
377 return n_noPerEmitterData;
378}
379
380QQuick3DParticleSpriteParticle::PerEmitterData &QQuick3DParticleSpriteParticle::perEmitterData(int emitterIndex)
381{
382 for (auto &perEmitter : m_perEmitterData) {
383 if (perEmitter.emitterIndex == emitterIndex)
384 return perEmitter;
385 }
386 return n_noPerEmitterData;
387}
388
389QSSGRenderGraphObject *QQuick3DParticleSpriteParticle::updateParticleNode(const ParticleUpdateNode *updateNode,
390 QSSGRenderGraphObject *node)
391{
392 if (!node) {
393 markAllDirty();
394 node = new QSSGRenderParticles();
395 }
396
397 auto particles = static_cast<QSSGRenderParticles *>(node);
398 const auto &perEmitter = perEmitterData(updateNode);
399
400 if (!updateNode->m_nodeDirty)
401 return particles;
402
403 if (perEmitter.particleCount == 0)
404 return particles;
405
406 if (m_sprite)
407 particles->m_sprite = m_sprite->getRenderImage();
408 else
409 particles->m_sprite = nullptr;
410
411 if (m_spriteSequence) {
412 particles->m_spriteImageCount = m_spriteSequence->m_frameCount;
413 particles->m_blendImages = m_spriteSequence->m_interpolate;
414 } else {
415 particles->m_spriteImageCount = 1;
416 particles->m_blendImages = true;
417 }
418
419 particles->m_hasTransparency = hasTransparency();
420
421 if (m_colorTable)
422 particles->m_colorTable = m_colorTable->getRenderImage();
423 else
424 particles->m_colorTable = nullptr;
425
426 if (!m_lights.isEmpty()) {
427 // Matches to QSSGRenderParticles lights
428 QVarLengthArray<QSSGRenderLight *, 4> lightNodes;
429 for (auto light : std::as_const(m_lights)) {
430 auto lightPrivate = QQuick3DObjectPrivate::get(light);
431 auto lightNode = static_cast<QSSGRenderLight *>(lightPrivate->spatialNode);
432 lightNodes.append(lightNode);
433 }
434 particles->m_lights = lightNodes;
435 }
436
437 particles->m_blendMode = mapBlendMode(m_blendMode);
438 particles->m_billboard = m_billboard;
439 particles->m_depthBiasSq = QSSGRenderNode::signedSquared(perEmitter.emitter->depthBias());
440 particles->m_featureLevel = mapFeatureLevel(m_featureLevel);
441 particles->m_depthSorting = sortMode() == QQuick3DParticle::SortDistance;
442 particles->m_castsReflections = m_castsReflections;
443
444 return particles;
445}
446
447void QQuick3DParticleSpriteParticle::handleMaxAmountChanged(int amount)
448{
449 if (m_particleData.size() == amount)
450 return;
451
452 m_particleData.resize(amount);
453 m_spriteParticleData.resize(amount);
454 reset();
455}
456
457void QQuick3DParticleSpriteParticle::handleSystemChanged(QQuick3DParticleSystem *system)
458{
459 for (PerEmitterData &value : m_perEmitterData) {
460 delete value.particleUpdateNode;
461 value.particleUpdateNode = new ParticleUpdateNode(system);
462 value.particleUpdateNode->m_particle = this;
463 }
464 updateNodeLayers();
465}
466
467void QQuick3DParticleSpriteParticle::updateNodes()
468{
469 for (const PerEmitterData &value : std::as_const(m_perEmitterData))
470 value.particleUpdateNode->update();
471}
472
473void QQuick3DParticleSpriteParticle::markNodesDirty()
474{
475 for (const PerEmitterData &value : std::as_const(m_perEmitterData))
476 value.particleUpdateNode->m_nodeDirty = true;
477}
478
479void QQuick3DParticleSpriteParticle::updateNodeLayers()
480{
481 auto *psystem = system();
482 if (!psystem)
483 return;
484 const int layers = psystem->layers();
485 for (const PerEmitterData &value : std::as_const(m_perEmitterData))
486 value.particleUpdateNode->setLayers(layers);
487}
488
489void QQuick3DParticleSpriteParticle::updateFeatureLevel()
490{
491 FeatureLevel featureLevel = FeatureLevel::Simple;
492 if (m_lights.isEmpty()) {
493 if (m_colorTable)
494 featureLevel = FeatureLevel::Mapped;
495 if (m_spriteSequence)
496 featureLevel = FeatureLevel::Animated;
497 } else {
498 featureLevel = FeatureLevel::SimpleVLight;
499 if (m_colorTable)
500 featureLevel = FeatureLevel::MappedVLight;
501 if (m_spriteSequence)
502 featureLevel = FeatureLevel::AnimatedVLight;
503 }
504 if (featureLevel != m_featureLevel)
505 m_featureLevel = featureLevel;
506}
507
508void QQuick3DParticleSpriteParticle::componentComplete()
509{
510 if (!system() && qobject_cast<QQuick3DParticleSystem *>(parentItem()))
511 setSystem(qobject_cast<QQuick3DParticleSystem *>(parentItem()));
512
513 QQuick3DParticle::componentComplete();
514}
515
516void QQuick3DParticleSpriteParticle::reset()
517{
518 QQuick3DParticle::reset();
519 deleteNodes();
520 m_nextEmitterIndex = 0;
521 m_spriteParticleData.fill({});
522}
523
524void QQuick3DParticleSpriteParticle::commitParticles(float)
525{
526 markAllDirty();
527 update();
528 updateNodes();
529}
530
531int QQuick3DParticleSpriteParticle::nextCurrentIndex(const QQuick3DParticleEmitter *emitter)
532{
533 if (!m_perEmitterData.contains(emitter)) {
534 m_perEmitterData.insert(emitter, PerEmitterData());
535 auto &perEmitter = m_perEmitterData[emitter];
536 perEmitter.particleUpdateNode = new ParticleUpdateNode(system());
537 perEmitter.emitter = emitter;
538 perEmitter.particleUpdateNode->m_particle = this;
539 perEmitter.emitterIndex = m_nextEmitterIndex++;
540 updateNodeLayers();
541 }
542 auto &perEmitter = m_perEmitterData[emitter];
543 int index = QQuick3DParticle::nextCurrentIndex(emitter);
544 if (m_spriteParticleData[index].emitterIndex != perEmitter.emitterIndex) {
545 if (m_spriteParticleData[index].emitterIndex >= 0)
546 perEmitterData(m_spriteParticleData[index].emitterIndex).particleCount--;
547 perEmitter.particleCount++;
548 }
549 m_spriteParticleData[index].emitterIndex = perEmitter.emitterIndex;
550 return index;
551}
552
553void QQuick3DParticleSpriteParticle::setParticleData(int particleIndex,
554 const QVector3D &position,
555 const QVector3D &rotation,
556 const QVector4D &color,
557 float size, float age,
558 float animationFrame)
559{
560 auto &dst = m_spriteParticleData[particleIndex];
561 dst = {position, rotation, color, size, age, animationFrame, dst.emitterIndex};
562}
563
564void QQuick3DParticleSpriteParticle::resetParticleData(int particleIndex)
565{
566 auto &dst = m_spriteParticleData[particleIndex];
567 if (dst.size > 0.0f)
568 dst = {{}, {}, {}, 0.0f, 0.0f, -1.0f, dst.emitterIndex};
569}
570
571void QQuick3DParticleSpriteParticle::updateParticleBuffer(ParticleUpdateNode *updateNode, QSSGRenderGraphObject *spatialNode)
572{
573 const auto &perEmitter = perEmitterData(updateNode);
574 const auto &particles = m_spriteParticleData;
575 QSSGRenderParticles *node = static_cast<QSSGRenderParticles *>(spatialNode);
576 if (!node)
577 return;
578 const int particleCount = perEmitter.particleCount;
579 if (node->m_particleBuffer.particleCount() != particleCount || m_useAnimatedParticle)
580 node->m_particleBuffer.resize(particleCount, sizeof(QSSGParticleSimple));
581
582 m_useAnimatedParticle = false;
583 char *dest = node->m_particleBuffer.pointer();
584 const SpriteParticleData *src = particles.data();
585 const int pps = node->m_particleBuffer.particlesPerSlice();
586 const int ss = node->m_particleBuffer.sliceStride();
587 const int slices = node->m_particleBuffer.sliceCount();
588 const int emitterIndex = perEmitter.emitterIndex;
589 int i = 0;
590 QSSGBounds3 bounds;
591 const auto smode = sortMode();
592 if (smode == QQuick3DParticle::SortNewest || smode == QQuick3DParticle::SortOldest) {
593 int offset = m_currentIndex;
594 int step = (smode == QQuick3DParticle::SortNewest) ? -1 : 1;
595 int li = 0;
596 const auto sourceIndex = [&](int linearIndex, int offset, int wrap) -> int {
597 return (linearIndex + offset + wrap) % wrap;
598 };
599 for (int s = 0; s < slices; s++) {
600 QSSGParticleSimple *dp = reinterpret_cast<QSSGParticleSimple *>(dest);
601 for (int p = 0; p < pps && i < particleCount; ) {
602 const SpriteParticleData *data = src + sourceIndex(li * step, offset, m_maxAmount);
603 if (data->emitterIndex == emitterIndex) {
604 if (data->size > 0.0f)
605 bounds.include(data->position);
606 dp->position = data->position;
607 dp->rotation = data->rotation * float(M_PI / 180.0f);
608 dp->color = data->color;
609 dp->size = data->size * m_particleScale;
610 dp->age = data->age;
611 dp++;
612 p++;
613 i++;
614 }
615 li++;
616 }
617 dest += ss;
618 }
619 } else {
620 for (int s = 0; s < slices; s++) {
621 QSSGParticleSimple *dp = reinterpret_cast<QSSGParticleSimple *>(dest);
622 for (int p = 0; p < pps && i < particleCount; ) {
623 if (src->emitterIndex == emitterIndex) {
624 if (src->size > 0.0f)
625 bounds.include(src->position);
626 dp->position = src->position;
627 dp->rotation = src->rotation * float(M_PI / 180.0f);
628 dp->color = src->color;
629 dp->size = src->size * m_particleScale;
630 dp->age = src->age;
631 dp++;
632 p++;
633 i++;
634 }
635 src++;
636 }
637 dest += ss;
638 }
639 }
640 node->m_particleBuffer.setBounds(bounds);
641}
642
643void QQuick3DParticleSpriteParticle::updateAnimatedParticleBuffer(ParticleUpdateNode *updateNode, QSSGRenderGraphObject *spatialNode)
644{
645 const auto &perEmitter = perEmitterData(updateNode);
646 const auto &particles = m_spriteParticleData;
647 QSSGRenderParticles *node = static_cast<QSSGRenderParticles *>(spatialNode);
648 if (!node)
649 return;
650 const int particleCount = perEmitter.particleCount;
651 if (node->m_particleBuffer.particleCount() != particleCount || !m_useAnimatedParticle)
652 node->m_particleBuffer.resize(particleCount, sizeof(QSSGParticleAnimated));
653
654 m_useAnimatedParticle = true;
655 char *dest = node->m_particleBuffer.pointer();
656 const SpriteParticleData *src = particles.data();
657 const int pps = node->m_particleBuffer.particlesPerSlice();
658 const int ss = node->m_particleBuffer.sliceStride();
659 const int slices = node->m_particleBuffer.sliceCount();
660 const int emitterIndex = perEmitter.emitterIndex;
661 int i = 0;
662 QSSGBounds3 bounds;
663 const auto smode = sortMode();
664 if (smode == QQuick3DParticle::SortNewest || smode == QQuick3DParticle::SortOldest) {
665 int offset = m_currentIndex;
666 int step = (smode == QQuick3DParticle::SortNewest) ? -1 : 1;
667 int li = 0;
668 const auto sourceIndex = [&](int linearIndex, int offset, int wrap) -> int {
669 return (linearIndex + offset + wrap) % wrap;
670 };
671 for (int s = 0; s < slices; s++) {
672 QSSGParticleAnimated *dp = reinterpret_cast<QSSGParticleAnimated *>(dest);
673 for (int p = 0; p < pps && i < particleCount; ) {
674 const SpriteParticleData *data = src + sourceIndex(li * step, offset, m_maxAmount);
675 if (data->emitterIndex == emitterIndex) {
676 if (data->size > 0.0f)
677 bounds.include(data->position);
678 dp->position = data->position;
679 dp->rotation = data->rotation * float(M_PI / 180.0f);
680 dp->color = data->color;
681 dp->size = data->size * m_particleScale;
682 dp->age = data->age;
683 dp->animationFrame = data->animationFrame;
684 dp++;
685 p++;
686 i++;
687 }
688 li++;
689 }
690 dest += ss;
691 }
692 } else {
693 for (int s = 0; s < slices; s++) {
694 QSSGParticleAnimated *dp = reinterpret_cast<QSSGParticleAnimated *>(dest);
695 for (int p = 0; p < pps && i < particleCount; ) {
696 if (src->emitterIndex == emitterIndex) {
697 if (src->size > 0.0f)
698 bounds.include(src->position);
699 dp->position = src->position;
700 dp->rotation = src->rotation * float(M_PI / 180.0f);
701 dp->color = src->color;
702 dp->size = src->size * m_particleScale;
703 dp->age = src->age;
704 dp->animationFrame = src->animationFrame;
705 dp++;
706 p++;
707 i++;
708 }
709 src++;
710 }
711 dest += ss;
712 }
713 }
714 node->m_particleBuffer.setBounds(bounds);
715}
716
717void QQuick3DParticleSpriteParticle::updateSceneManager(QQuick3DSceneManager *sceneManager)
718{
719 // Check all the resource value's scene manager, and update as necessary.
720 if (sceneManager) {
721 QQuick3DObjectPrivate::refSceneManager(m_sprite, *sceneManager);
722 QQuick3DObjectPrivate::refSceneManager(m_colorTable, *sceneManager);
723 } else {
724 QQuick3DObjectPrivate::derefSceneManager(m_sprite);
725 QQuick3DObjectPrivate::derefSceneManager(m_colorTable);
726 }
727}
728
729// Lights
730void QQuick3DParticleSpriteParticle::onLightDestroyed(QObject *object)
731{
732 bool found = false;
733 for (int i = 0; i < m_lights.size(); ++i) {
734 if (m_lights[i] == object) {
735 m_lights.removeAt(i--);
736 found = true;
737 }
738 }
739 if (found) {
740 updateFeatureLevel();
741 markNodesDirty();
742 }
743}
744
745void QQuick3DParticleSpriteParticle::qmlAppendLight(QQmlListProperty<QQuick3DAbstractLight> *list, QQuick3DAbstractLight *light)
746{
747 if (!light)
748 return;
749
750 // Light must be id of an existing View3D light and not inline light element
751 if (light->parentItem()) {
752 QQuick3DParticleSpriteParticle *self = static_cast<QQuick3DParticleSpriteParticle *>(list->object);
753 self->m_lights.push_back(light);
754 self->updateFeatureLevel();
755 self->markNodesDirty();
756 // Make sure ligths are removed when destroyed
757 connect(light, &QQuick3DParticleSpriteParticle::destroyed, self, &QQuick3DParticleSpriteParticle::onLightDestroyed);
758 }
759}
760
761QQuick3DAbstractLight *QQuick3DParticleSpriteParticle::qmlLightAt(QQmlListProperty<QQuick3DAbstractLight> *list, qsizetype index)
762{
763 QQuick3DParticleSpriteParticle *self = static_cast<QQuick3DParticleSpriteParticle *>(list->object);
764 if (index >= self->m_lights.size()) {
765 qWarning("The index exceeds the range of valid light targets.");
766 return nullptr;
767 }
768 return self->m_lights.at(index);
769}
770
771qsizetype QQuick3DParticleSpriteParticle::qmlLightsCount(QQmlListProperty<QQuick3DAbstractLight> *list)
772{
773 QQuick3DParticleSpriteParticle *self = static_cast<QQuick3DParticleSpriteParticle *>(list->object);
774 return self->m_lights.size();
775}
776
777void QQuick3DParticleSpriteParticle::qmlClearLights(QQmlListProperty<QQuick3DAbstractLight> *list)
778{
779 QQuick3DParticleSpriteParticle *self = static_cast<QQuick3DParticleSpriteParticle *>(list->object);
780 for (const auto &light : std::as_const(self->m_lights)) {
781 if (light->parentItem() == nullptr)
782 QQuick3DObjectPrivate::get(light)->derefSceneManager();
783 disconnect(light, &QQuick3DParticleSpriteParticle::destroyed, self, &QQuick3DParticleSpriteParticle::onLightDestroyed);
784 }
785 self->m_lights.clear();
786 self->updateFeatureLevel();
787 self->markNodesDirty();
788}
789
790QT_END_NAMESPACE
Combined button and popup list for selecting options.
static QSSGRenderParticles::BlendMode mapBlendMode(QQuick3DParticleSpriteParticle::BlendMode mode)