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

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