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}
465
466void QQuick3DParticleSpriteParticle::updateNodes()
467{
468 for (const PerEmitterData &value : std::as_const(m_perEmitterData))
469 value.particleUpdateNode->update();
470}
471
472void QQuick3DParticleSpriteParticle::markNodesDirty()
473{
474 for (const PerEmitterData &value : std::as_const(m_perEmitterData))
475 value.particleUpdateNode->m_nodeDirty = true;
476}
477
478void QQuick3DParticleSpriteParticle::updateFeatureLevel()
479{
480 FeatureLevel featureLevel = FeatureLevel::Simple;
481 if (m_lights.isEmpty()) {
482 if (m_colorTable)
483 featureLevel = FeatureLevel::Mapped;
484 if (m_spriteSequence)
485 featureLevel = FeatureLevel::Animated;
486 } else {
487 featureLevel = FeatureLevel::SimpleVLight;
488 if (m_colorTable)
489 featureLevel = FeatureLevel::MappedVLight;
490 if (m_spriteSequence)
491 featureLevel = FeatureLevel::AnimatedVLight;
492 }
493 if (featureLevel != m_featureLevel)
494 m_featureLevel = featureLevel;
495}
496
497void QQuick3DParticleSpriteParticle::componentComplete()
498{
499 if (!system() && qobject_cast<QQuick3DParticleSystem *>(parentItem()))
500 setSystem(qobject_cast<QQuick3DParticleSystem *>(parentItem()));
501
502 QQuick3DParticle::componentComplete();
503}
504
505void QQuick3DParticleSpriteParticle::reset()
506{
507 QQuick3DParticle::reset();
508 deleteNodes();
509 m_nextEmitterIndex = 0;
510 m_spriteParticleData.fill({});
511}
512
513void QQuick3DParticleSpriteParticle::commitParticles(float)
514{
515 markAllDirty();
516 update();
517 updateNodes();
518}
519
520int QQuick3DParticleSpriteParticle::nextCurrentIndex(const QQuick3DParticleEmitter *emitter)
521{
522 if (!m_perEmitterData.contains(emitter)) {
523 m_perEmitterData.insert(emitter, PerEmitterData());
524 auto &perEmitter = m_perEmitterData[emitter];
525 perEmitter.particleUpdateNode = new ParticleUpdateNode(system());
526 perEmitter.emitter = emitter;
527 perEmitter.particleUpdateNode->m_particle = this;
528 perEmitter.emitterIndex = m_nextEmitterIndex++;
529 }
530 auto &perEmitter = m_perEmitterData[emitter];
531 int index = QQuick3DParticle::nextCurrentIndex(emitter);
532 if (m_spriteParticleData[index].emitterIndex != perEmitter.emitterIndex) {
533 if (m_spriteParticleData[index].emitterIndex >= 0)
534 perEmitterData(m_spriteParticleData[index].emitterIndex).particleCount--;
535 perEmitter.particleCount++;
536 }
537 m_spriteParticleData[index].emitterIndex = perEmitter.emitterIndex;
538 return index;
539}
540
541void QQuick3DParticleSpriteParticle::setParticleData(int particleIndex,
542 const QVector3D &position,
543 const QVector3D &rotation,
544 const QVector4D &color,
545 float size, float age,
546 float animationFrame)
547{
548 auto &dst = m_spriteParticleData[particleIndex];
549 dst = {position, rotation, color, size, age, animationFrame, dst.emitterIndex};
550}
551
552void QQuick3DParticleSpriteParticle::resetParticleData(int particleIndex)
553{
554 auto &dst = m_spriteParticleData[particleIndex];
555 if (dst.size > 0.0f)
556 dst = {{}, {}, {}, 0.0f, 0.0f, -1.0f, dst.emitterIndex};
557}
558
559void QQuick3DParticleSpriteParticle::updateParticleBuffer(ParticleUpdateNode *updateNode, QSSGRenderGraphObject *spatialNode)
560{
561 const auto &perEmitter = perEmitterData(updateNode);
562 const auto &particles = m_spriteParticleData;
563 QSSGRenderParticles *node = static_cast<QSSGRenderParticles *>(spatialNode);
564 if (!node)
565 return;
566 const int particleCount = perEmitter.particleCount;
567 if (node->m_particleBuffer.particleCount() != particleCount || m_useAnimatedParticle)
568 node->m_particleBuffer.resize(particleCount, sizeof(QSSGParticleSimple));
569
570 m_useAnimatedParticle = false;
571 char *dest = node->m_particleBuffer.pointer();
572 const SpriteParticleData *src = particles.data();
573 const int pps = node->m_particleBuffer.particlesPerSlice();
574 const int ss = node->m_particleBuffer.sliceStride();
575 const int slices = node->m_particleBuffer.sliceCount();
576 const int emitterIndex = perEmitter.emitterIndex;
577 int i = 0;
578 QSSGBounds3 bounds;
579 const auto smode = sortMode();
580 if (smode == QQuick3DParticle::SortNewest || smode == QQuick3DParticle::SortOldest) {
581 int offset = m_currentIndex;
582 int step = (smode == QQuick3DParticle::SortNewest) ? -1 : 1;
583 int li = 0;
584 const auto sourceIndex = [&](int linearIndex, int offset, int wrap) -> int {
585 return (linearIndex + offset + wrap) % wrap;
586 };
587 for (int s = 0; s < slices; s++) {
588 QSSGParticleSimple *dp = reinterpret_cast<QSSGParticleSimple *>(dest);
589 for (int p = 0; p < pps && i < particleCount; ) {
590 const SpriteParticleData *data = src + sourceIndex(li * step, offset, m_maxAmount);
591 if (data->emitterIndex == emitterIndex) {
592 if (data->size > 0.0f)
593 bounds.include(data->position);
594 dp->position = data->position;
595 dp->rotation = data->rotation * float(M_PI / 180.0f);
596 dp->color = data->color;
597 dp->size = data->size * m_particleScale;
598 dp->age = data->age;
599 dp++;
600 p++;
601 i++;
602 }
603 li++;
604 }
605 dest += ss;
606 }
607 } else {
608 for (int s = 0; s < slices; s++) {
609 QSSGParticleSimple *dp = reinterpret_cast<QSSGParticleSimple *>(dest);
610 for (int p = 0; p < pps && i < particleCount; ) {
611 if (src->emitterIndex == emitterIndex) {
612 if (src->size > 0.0f)
613 bounds.include(src->position);
614 dp->position = src->position;
615 dp->rotation = src->rotation * float(M_PI / 180.0f);
616 dp->color = src->color;
617 dp->size = src->size * m_particleScale;
618 dp->age = src->age;
619 dp++;
620 p++;
621 i++;
622 }
623 src++;
624 }
625 dest += ss;
626 }
627 }
628 node->m_particleBuffer.setBounds(bounds);
629}
630
631void QQuick3DParticleSpriteParticle::updateAnimatedParticleBuffer(ParticleUpdateNode *updateNode, QSSGRenderGraphObject *spatialNode)
632{
633 const auto &perEmitter = perEmitterData(updateNode);
634 const auto &particles = m_spriteParticleData;
635 QSSGRenderParticles *node = static_cast<QSSGRenderParticles *>(spatialNode);
636 if (!node)
637 return;
638 const int particleCount = perEmitter.particleCount;
639 if (node->m_particleBuffer.particleCount() != particleCount || !m_useAnimatedParticle)
640 node->m_particleBuffer.resize(particleCount, sizeof(QSSGParticleAnimated));
641
642 m_useAnimatedParticle = true;
643 char *dest = node->m_particleBuffer.pointer();
644 const SpriteParticleData *src = particles.data();
645 const int pps = node->m_particleBuffer.particlesPerSlice();
646 const int ss = node->m_particleBuffer.sliceStride();
647 const int slices = node->m_particleBuffer.sliceCount();
648 const int emitterIndex = perEmitter.emitterIndex;
649 int i = 0;
650 QSSGBounds3 bounds;
651 const auto smode = sortMode();
652 if (smode == QQuick3DParticle::SortNewest || smode == QQuick3DParticle::SortOldest) {
653 int offset = m_currentIndex;
654 int step = (smode == QQuick3DParticle::SortNewest) ? -1 : 1;
655 int li = 0;
656 const auto sourceIndex = [&](int linearIndex, int offset, int wrap) -> int {
657 return (linearIndex + offset + wrap) % wrap;
658 };
659 for (int s = 0; s < slices; s++) {
660 QSSGParticleAnimated *dp = reinterpret_cast<QSSGParticleAnimated *>(dest);
661 for (int p = 0; p < pps && i < particleCount; ) {
662 const SpriteParticleData *data = src + sourceIndex(li * step, offset, m_maxAmount);
663 if (data->emitterIndex == emitterIndex) {
664 if (data->size > 0.0f)
665 bounds.include(data->position);
666 dp->position = data->position;
667 dp->rotation = data->rotation * float(M_PI / 180.0f);
668 dp->color = data->color;
669 dp->size = data->size * m_particleScale;
670 dp->age = data->age;
671 dp->animationFrame = data->animationFrame;
672 dp++;
673 p++;
674 i++;
675 }
676 li++;
677 }
678 dest += ss;
679 }
680 } else {
681 for (int s = 0; s < slices; s++) {
682 QSSGParticleAnimated *dp = reinterpret_cast<QSSGParticleAnimated *>(dest);
683 for (int p = 0; p < pps && i < particleCount; ) {
684 if (src->emitterIndex == emitterIndex) {
685 if (src->size > 0.0f)
686 bounds.include(src->position);
687 dp->position = src->position;
688 dp->rotation = src->rotation * float(M_PI / 180.0f);
689 dp->color = src->color;
690 dp->size = src->size * m_particleScale;
691 dp->age = src->age;
692 dp->animationFrame = src->animationFrame;
693 dp++;
694 p++;
695 i++;
696 }
697 src++;
698 }
699 dest += ss;
700 }
701 }
702 node->m_particleBuffer.setBounds(bounds);
703}
704
705void QQuick3DParticleSpriteParticle::updateSceneManager(QQuick3DSceneManager *sceneManager)
706{
707 // Check all the resource value's scene manager, and update as necessary.
708 if (sceneManager) {
709 QQuick3DObjectPrivate::refSceneManager(m_sprite, *sceneManager);
710 QQuick3DObjectPrivate::refSceneManager(m_colorTable, *sceneManager);
711 } else {
712 QQuick3DObjectPrivate::derefSceneManager(m_sprite);
713 QQuick3DObjectPrivate::derefSceneManager(m_colorTable);
714 }
715}
716
717// Lights
718void QQuick3DParticleSpriteParticle::onLightDestroyed(QObject *object)
719{
720 bool found = false;
721 for (int i = 0; i < m_lights.size(); ++i) {
722 if (m_lights[i] == object) {
723 m_lights.removeAt(i--);
724 found = true;
725 }
726 }
727 if (found) {
728 updateFeatureLevel();
729 markNodesDirty();
730 }
731}
732
733void QQuick3DParticleSpriteParticle::qmlAppendLight(QQmlListProperty<QQuick3DAbstractLight> *list, QQuick3DAbstractLight *light)
734{
735 if (!light)
736 return;
737
738 // Light must be id of an existing View3D light and not inline light element
739 if (light->parentItem()) {
740 QQuick3DParticleSpriteParticle *self = static_cast<QQuick3DParticleSpriteParticle *>(list->object);
741 self->m_lights.push_back(light);
742 self->updateFeatureLevel();
743 self->markNodesDirty();
744 // Make sure ligths are removed when destroyed
745 connect(light, &QQuick3DParticleSpriteParticle::destroyed, self, &QQuick3DParticleSpriteParticle::onLightDestroyed);
746 }
747}
748
749QQuick3DAbstractLight *QQuick3DParticleSpriteParticle::qmlLightAt(QQmlListProperty<QQuick3DAbstractLight> *list, qsizetype index)
750{
751 QQuick3DParticleSpriteParticle *self = static_cast<QQuick3DParticleSpriteParticle *>(list->object);
752 if (index >= self->m_lights.size()) {
753 qWarning("The index exceeds the range of valid light targets.");
754 return nullptr;
755 }
756 return self->m_lights.at(index);
757}
758
759qsizetype QQuick3DParticleSpriteParticle::qmlLightsCount(QQmlListProperty<QQuick3DAbstractLight> *list)
760{
761 QQuick3DParticleSpriteParticle *self = static_cast<QQuick3DParticleSpriteParticle *>(list->object);
762 return self->m_lights.size();
763}
764
765void QQuick3DParticleSpriteParticle::qmlClearLights(QQmlListProperty<QQuick3DAbstractLight> *list)
766{
767 QQuick3DParticleSpriteParticle *self = static_cast<QQuick3DParticleSpriteParticle *>(list->object);
768 for (const auto &light : std::as_const(self->m_lights)) {
769 if (light->parentItem() == nullptr)
770 QQuick3DObjectPrivate::get(light)->derefSceneManager();
771 disconnect(light, &QQuick3DParticleSpriteParticle::destroyed, self, &QQuick3DParticleSpriteParticle::onLightDestroyed);
772 }
773 self->m_lights.clear();
774 self->updateFeatureLevel();
775 self->markNodesDirty();
776}
777
778QT_END_NAMESPACE
Combined button and popup list for selecting options.
static QSSGRenderParticles::BlendMode mapBlendMode(QQuick3DParticleSpriteParticle::BlendMode mode)