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 | |
11 | QT_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 | |
24 | QQuick3DParticleSpriteParticle::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 | |
38 | QQuick3DParticleSpriteParticle::~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 | |
50 | void 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 | */ |
79 | QQuick3DParticleSpriteParticle::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 | */ |
101 | QQuick3DTexture *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 | |
114 | QQuick3DParticleSpriteSequence *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 | */ |
130 | bool 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 | */ |
147 | float 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 | */ |
162 | QQuick3DTexture *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 | |
177 | QQmlListProperty<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 | */ |
193 | float 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 | */ |
204 | float 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 | */ |
216 | bool QQuick3DParticleSpriteParticle::castsReflections() const |
217 | { |
218 | return m_castsReflections; |
219 | } |
220 | |
221 | void QQuick3DParticleSpriteParticle::setBlendMode(BlendMode blendMode) |
222 | { |
223 | if (m_blendMode == blendMode) |
224 | return; |
225 | m_blendMode = blendMode; |
226 | markNodesDirty(); |
227 | Q_EMIT blendModeChanged(); |
228 | } |
229 | |
230 | void 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 | |
242 | void 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 | |
253 | void QQuick3DParticleSpriteParticle::setBillboard(bool billboard) |
254 | { |
255 | if (m_billboard == billboard) |
256 | return; |
257 | m_billboard = billboard; |
258 | markNodesDirty(); |
259 | Q_EMIT billboardChanged(); |
260 | } |
261 | |
262 | void 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 | |
271 | void 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 | |
284 | void 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 | |
293 | void 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 | |
302 | void QQuick3DParticleSpriteParticle::setCastsReflections(bool castsReflections) |
303 | { |
304 | if (m_castsReflections == castsReflections) |
305 | return; |
306 | m_castsReflections = castsReflections; |
307 | emit castsReflectionsChanged(); |
308 | } |
309 | |
310 | void QQuick3DParticleSpriteParticle::itemChange(QQuick3DObject::ItemChange change, |
311 | const QQuick3DObject::ItemChangeData &value) |
312 | { |
313 | if (change == QQuick3DObject::ItemSceneChange) |
314 | updateSceneManager(window: value.sceneManager); |
315 | } |
316 | |
317 | static 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 | |
331 | QSSGRenderParticles::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 | |
351 | QSSGRenderGraphObject *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 | |
369 | QQuick3DParticleSpriteParticle::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 | |
378 | QQuick3DParticleSpriteParticle::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 | |
387 | QSSGRenderGraphObject *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 | |
445 | void 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 | |
455 | void 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 | |
464 | void QQuick3DParticleSpriteParticle::updateNodes() |
465 | { |
466 | for (const PerEmitterData &value : std::as_const(t&: m_perEmitterData)) |
467 | value.particleUpdateNode->update(); |
468 | } |
469 | |
470 | void QQuick3DParticleSpriteParticle::markNodesDirty() |
471 | { |
472 | for (const PerEmitterData &value : std::as_const(t&: m_perEmitterData)) |
473 | value.particleUpdateNode->m_nodeDirty = true; |
474 | } |
475 | |
476 | void 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 | |
495 | void QQuick3DParticleSpriteParticle::componentComplete() |
496 | { |
497 | if (!system() && qobject_cast<QQuick3DParticleSystem *>(object: parentItem())) |
498 | setSystem(qobject_cast<QQuick3DParticleSystem *>(object: parentItem())); |
499 | |
500 | QQuick3DParticle::componentComplete(); |
501 | } |
502 | |
503 | void QQuick3DParticleSpriteParticle::reset() |
504 | { |
505 | QQuick3DParticle::reset(); |
506 | deleteNodes(); |
507 | m_nextEmitterIndex = 0; |
508 | m_spriteParticleData.fill(t: {}); |
509 | } |
510 | |
511 | void QQuick3DParticleSpriteParticle::commitParticles(float) |
512 | { |
513 | markAllDirty(); |
514 | update(); |
515 | updateNodes(); |
516 | } |
517 | |
518 | int 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 | |
539 | void 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 | |
550 | void 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 | |
557 | void 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 | |
629 | void 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 | |
703 | void 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 |
716 | void 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 | |
731 | void 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 | |
747 | QQuick3DAbstractLight *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 | |
757 | qsizetype QQuick3DParticleSpriteParticle::qmlLightsCount(QQmlListProperty<QQuick3DAbstractLight> *list) |
758 | { |
759 | QQuick3DParticleSpriteParticle *self = static_cast<QQuick3DParticleSpriteParticle *>(list->object); |
760 | return self->m_lights.size(); |
761 | } |
762 | |
763 | void 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 | |
776 | QT_END_NAMESPACE |
777 | |