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