1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qquick3dparticlelineparticle_p.h" |
5 | #include "qquick3dparticlesystem_p.h" |
6 | #include "qquick3dparticlerandomizer_p.h" |
7 | #include "qquick3dparticleutils_p.h" |
8 | #include <private/qquick3dobject_p.h> |
9 | |
10 | #include <algorithm> |
11 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | /*! |
15 | \qmltype LineParticle3D |
16 | \inherits SpriteParticle3D |
17 | \inqmlmodule QtQuick3D.Particles3D |
18 | \brief Line particle. |
19 | \since 6.4 |
20 | |
21 | The LineParticle3D creates line shaped sprite particles. |
22 | |
23 | The line is created from the path of the particle when it moves. |
24 | The length of the line is specified either by the \l length parameter |
25 | or the segment count and minimum delta between points. In latter case the |
26 | length of the line may vary if the particles speed varies. |
27 | */ |
28 | |
29 | QQuick3DParticleLineParticle::QQuick3DParticleLineParticle(QQuick3DNode *parent) |
30 | : QQuick3DParticleSpriteParticle(parent) |
31 | { |
32 | } |
33 | |
34 | QQuick3DParticleLineParticle::~QQuick3DParticleLineParticle() |
35 | { |
36 | } |
37 | |
38 | /*! |
39 | \qmlproperty int LineParticle3D::segmentCount |
40 | |
41 | This property holds the number of segments in the line. The line is drawn using |
42 | segment + 1 points, where the additional one comes from the particles current position. |
43 | The default value is 1. |
44 | */ |
45 | int QQuick3DParticleLineParticle::segmentCount() const |
46 | { |
47 | return m_segmentCount; |
48 | } |
49 | |
50 | /*! |
51 | \qmlproperty real LineParticle3D::alphaFade |
52 | |
53 | This property holds the alpha fade factor of the line. The alphaFade value range is [0, 1]. |
54 | When the value is greater than 0.0, causes the line to fade the further the segment is from the |
55 | first particle segment. The alpha for a segment is calculated like this: |
56 | segmentAlpha(s) = (1.0 - alphaFade) ^ s, where s is the segment index. |
57 | The default value is 0.0. |
58 | */ |
59 | float QQuick3DParticleLineParticle::alphaFade() const |
60 | { |
61 | return m_alphaFade; |
62 | } |
63 | |
64 | /*! |
65 | \qmlproperty real LineParticle3D::scaleMultiplier |
66 | |
67 | This property holds the scale multiplier of the line. The scaleMultiplier value range is [0, 2]. |
68 | The scaleMultiplier modifies the line size for the line segments. If the value is less than 1.0, |
69 | the line gets smaller the further a segment is from the first segment and if the value is greater |
70 | than 1.0 the line gets bigger. The size for a segment is calculated like this: |
71 | size(s) = scaleMultiplier ^ s, where s is the segment index. |
72 | */ |
73 | float QQuick3DParticleLineParticle::scaleMultiplier() const |
74 | { |
75 | return m_scaleMultiplier; |
76 | } |
77 | |
78 | /*! |
79 | \qmlproperty real LineParticle3D::texcoordMultiplier |
80 | |
81 | This property holds the texture coordinate multiplier of the line. This value is factored |
82 | to the texture coordinate values of the line. The default value is 1.0. |
83 | */ |
84 | float QQuick3DParticleLineParticle::texcoordMultiplier() const |
85 | { |
86 | return m_texcoordMultiplier; |
87 | } |
88 | |
89 | /*! |
90 | \qmlproperty real LineParticle3D::length |
91 | |
92 | This property holds the length of the line. If the value is set, the lines length is limited |
93 | to the value. In this case the minimum delta of the line is the length divided |
94 | by the segment count. If the value is not set, the line length varies based on the |
95 | speed the particle moves as well as segment count and minimum delta. The default value is -1.0. |
96 | */ |
97 | float QQuick3DParticleLineParticle::length() const |
98 | { |
99 | return m_length; |
100 | } |
101 | |
102 | /*! |
103 | \qmlproperty real LineParticle3D::lengthVariation |
104 | |
105 | This property holds the length variation of the line. This parameter is not used if |
106 | the length parameter has not been set. When the length is set, this parameter can be used to |
107 | vary the length of each line. The default value is 0.0. |
108 | */ |
109 | float QQuick3DParticleLineParticle::lengthVariation() const |
110 | { |
111 | return m_lengthVariation; |
112 | } |
113 | |
114 | /*! |
115 | \qmlproperty real LineParticle3D::lengthDeltaMin |
116 | |
117 | This property holds the minimum length between segment points. This parameter is |
118 | ignored if the length parameter is set. The default value is 10.0. |
119 | */ |
120 | float QQuick3DParticleLineParticle::lengthDeltaMin() const |
121 | { |
122 | return m_lengthDeltaMin; |
123 | } |
124 | |
125 | /*! |
126 | \qmlproperty int LineParticle3D::eolFadeOutDuration |
127 | |
128 | This property holds the end-of-life fade-out duration of the line. If set, each line remains |
129 | in the place it was when the particle reached end of its lifetime, then fades out during this |
130 | time period. The default value is 0. |
131 | */ |
132 | int QQuick3DParticleLineParticle::eolFadeOutDuration() const |
133 | { |
134 | return m_eolFadeOutDuration; |
135 | } |
136 | |
137 | /*! |
138 | \qmlproperty enumeration LineParticle3D::TexcoordMode |
139 | |
140 | Defines the texture coordinate mode of line particle. |
141 | |
142 | \value LineParticle3D.Absolute |
143 | Texture coordinates are specified relative to the world position. |
144 | \value LineParticle3D.Relative |
145 | Texture coordinates are specified relative to line first line point. |
146 | \value LineParticle3D.Fill |
147 | Texture coordinates are specified such that the texture fills the whole line. |
148 | */ |
149 | |
150 | /*! |
151 | \qmlproperty TexcoordMode LineParticle3D::texcoordMode |
152 | |
153 | This property holds the texture coordinate mode of the line. |
154 | */ |
155 | QQuick3DParticleLineParticle::TexcoordMode QQuick3DParticleLineParticle::texcoordMode() const |
156 | { |
157 | return m_texcoordMode; |
158 | } |
159 | |
160 | void QQuick3DParticleLineParticle::setSegmentCount(int count) |
161 | { |
162 | count = qMax(a: 1, b: count); |
163 | if (m_segmentCount == count) |
164 | return; |
165 | m_segmentCount = count; |
166 | handleSegmentCountChanged(); |
167 | Q_EMIT segmentCountChanged(); |
168 | } |
169 | |
170 | void QQuick3DParticleLineParticle::setAlphaFade(float fade) |
171 | { |
172 | fade = qBound(min: 0.0f, val: fade, max: 1.0f); |
173 | if (qFuzzyCompare(p1: m_alphaFade, p2: fade)) |
174 | return; |
175 | m_alphaFade = fade; |
176 | Q_EMIT alphaFadeChanged(); |
177 | } |
178 | |
179 | void QQuick3DParticleLineParticle::setScaleMultiplier(float multiplier) |
180 | { |
181 | multiplier = qBound(min: 0.0f, val: multiplier, max: 2.0f); |
182 | if (qFuzzyCompare(p1: m_scaleMultiplier, p2: multiplier)) |
183 | return; |
184 | m_scaleMultiplier = multiplier; |
185 | Q_EMIT scaleMultiplierChanged(); |
186 | } |
187 | |
188 | void QQuick3DParticleLineParticle::setTexcoordMultiplier(float multiplier) |
189 | { |
190 | if (qFuzzyCompare(p1: m_texcoordMultiplier, p2: multiplier)) |
191 | return; |
192 | m_texcoordMultiplier = multiplier; |
193 | Q_EMIT texcoordMultiplierChanged(); |
194 | } |
195 | |
196 | void QQuick3DParticleLineParticle::setLength(float length) |
197 | { |
198 | length = length != -1.0f ? qMax(a: length, b: 0.0f) : -1.0f; |
199 | if (qFuzzyCompare(p1: m_length, p2: length)) |
200 | return; |
201 | m_length = length; |
202 | Q_EMIT lengthChanged(); |
203 | } |
204 | |
205 | void QQuick3DParticleLineParticle::setLengthVariation(float lengthVariation) |
206 | { |
207 | lengthVariation = qMax(a: lengthVariation, b: 0.0f); |
208 | if (qFuzzyCompare(p1: m_lengthVariation, p2: lengthVariation)) |
209 | return; |
210 | m_lengthVariation = lengthVariation; |
211 | Q_EMIT lengthVariationChanged(); |
212 | } |
213 | |
214 | void QQuick3DParticleLineParticle::setLengthDeltaMin(float min) |
215 | { |
216 | min = qMax(a: min, b: 0.0f); |
217 | if (qFuzzyCompare(p1: m_lengthDeltaMin, p2: min)) |
218 | return; |
219 | m_lengthDeltaMin = min; |
220 | Q_EMIT lengthDeltaMinChanged(); |
221 | } |
222 | |
223 | void QQuick3DParticleLineParticle::setEolFadeOutDuration(int duration) |
224 | { |
225 | duration = qMax(a: 0, b: duration); |
226 | if (duration == m_eolFadeOutDuration) |
227 | return; |
228 | m_eolFadeOutDuration = duration; |
229 | Q_EMIT eolFadeOutDurationChanged(); |
230 | } |
231 | |
232 | void QQuick3DParticleLineParticle::setTexcoordMode(TexcoordMode mode) |
233 | { |
234 | if (mode == m_texcoordMode) |
235 | return; |
236 | m_texcoordMode = mode; |
237 | Q_EMIT texcoordModeChanged(); |
238 | } |
239 | |
240 | QSSGRenderParticles::FeatureLevel lineFeatureLevel(QQuick3DParticleSpriteParticle::FeatureLevel in) |
241 | { |
242 | switch (in) { |
243 | case QQuick3DParticleSpriteParticle::FeatureLevel::Simple: |
244 | return QSSGRenderParticles::FeatureLevel::Line; |
245 | case QQuick3DParticleSpriteParticle::FeatureLevel::Mapped: |
246 | return QSSGRenderParticles::FeatureLevel::LineMapped; |
247 | case QQuick3DParticleSpriteParticle::FeatureLevel::Animated: |
248 | return QSSGRenderParticles::FeatureLevel::LineAnimated; |
249 | case QQuick3DParticleSpriteParticle::FeatureLevel::SimpleVLight: |
250 | return QSSGRenderParticles::FeatureLevel::LineVLight; |
251 | case QQuick3DParticleSpriteParticle::FeatureLevel::MappedVLight: |
252 | return QSSGRenderParticles::FeatureLevel::LineMappedVLight; |
253 | case QQuick3DParticleSpriteParticle::FeatureLevel::AnimatedVLight: |
254 | return QSSGRenderParticles::FeatureLevel::LineAnimatedVLight; |
255 | } |
256 | return QSSGRenderParticles::FeatureLevel::Line; |
257 | } |
258 | |
259 | QSSGRenderGraphObject *QQuick3DParticleLineParticle::updateLineNode(QSSGRenderGraphObject *node) |
260 | { |
261 | auto particles = static_cast<QSSGRenderParticles *>(node); |
262 | |
263 | float frames = 1.0f; |
264 | if (sprite() && spriteSequence()) |
265 | frames = float(spriteSequence()->frameCount()); |
266 | |
267 | particles->m_sizeModifier = m_scaleMultiplier; |
268 | particles->m_alphaFade = 1.0f - m_alphaFade; |
269 | if (m_texcoordMode == TexcoordMode::Fill) |
270 | particles->m_texcoordScale = frames * m_texcoordMultiplier; |
271 | else |
272 | particles->m_texcoordScale = frames / particleScale() * m_texcoordMultiplier; |
273 | particles->m_featureLevel = lineFeatureLevel(in: m_featureLevel); |
274 | |
275 | return particles; |
276 | } |
277 | |
278 | void QQuick3DParticleLineParticle::handleMaxAmountChanged(int amount) |
279 | { |
280 | if (m_lineData.size() == amount) |
281 | return; |
282 | |
283 | m_lineData.resize(size: m_segmentCount * amount); |
284 | m_lineHeaderData.resize(size: amount); |
285 | QQuick3DParticleSpriteParticle::handleMaxAmountChanged(amount); |
286 | } |
287 | |
288 | void QQuick3DParticleLineParticle::handleSystemChanged(QQuick3DParticleSystem *system) |
289 | { |
290 | for (PerEmitterData &value : m_perEmitterData) { |
291 | delete value.particleUpdateNode; |
292 | value.particleUpdateNode = new LineParticleUpdateNode(system); |
293 | value.particleUpdateNode->m_particle = this; |
294 | } |
295 | } |
296 | |
297 | QSSGRenderGraphObject *QQuick3DParticleLineParticle::LineParticleUpdateNode::updateSpatialNode(QSSGRenderGraphObject *node) |
298 | { |
299 | if (m_particle) { |
300 | QQuick3DParticleLineParticle *lineParticle = qobject_cast<QQuick3DParticleLineParticle *>(object: m_particle); |
301 | node = lineParticle->updateParticleNode(updateNode: this, node); |
302 | lineParticle->updateLineNode(node); |
303 | QQuick3DNode::updateSpatialNode(node); |
304 | Q_QUICK3D_PROFILE_ASSIGN_ID_SG(lineParticle, node); |
305 | auto particles = static_cast<QSSGRenderParticles *>(node); |
306 | |
307 | lineParticle->updateLineBuffer(updateNode: this, node: particles); |
308 | |
309 | m_nodeDirty = false; |
310 | } |
311 | return node; |
312 | } |
313 | |
314 | void QQuick3DParticleLineParticle ::reset() |
315 | { |
316 | QQuick3DParticleSpriteParticle::reset(); |
317 | m_lineData.fill(t: {}); |
318 | m_lineHeaderData.fill(t: {}); |
319 | m_fadeOutData.clear(); |
320 | } |
321 | |
322 | void QQuick3DParticleLineParticle::commitParticles(float time) |
323 | { |
324 | QQuick3DParticleSpriteParticle::commitParticles(time); |
325 | |
326 | for (auto iter = m_fadeOutData.begin(); iter != m_fadeOutData.end(); ) { |
327 | if (time >= iter->beginTime && time < iter->endTime) |
328 | iter++; |
329 | else |
330 | iter = m_fadeOutData.erase(pos: iter); |
331 | } |
332 | } |
333 | |
334 | int QQuick3DParticleLineParticle::nextCurrentIndex(const QQuick3DParticleEmitter *emitter) |
335 | { |
336 | if (!m_perEmitterData.contains(key: emitter)) { |
337 | m_perEmitterData.insert(key: emitter, value: PerEmitterData()); |
338 | auto &perEmitter = m_perEmitterData[emitter]; |
339 | perEmitter.particleUpdateNode = new LineParticleUpdateNode(system()); |
340 | perEmitter.emitter = emitter; |
341 | perEmitter.particleUpdateNode->m_particle = this; |
342 | perEmitter.emitterIndex = m_nextEmitterIndex++; |
343 | } |
344 | int index = QQuick3DParticleSpriteParticle::nextCurrentIndex(emitter); |
345 | clearSegment(particleIndex: index); |
346 | m_lineHeaderData[index].emitterIndex = m_perEmitterData[emitter].emitterIndex; |
347 | if (m_length > 0.0f) |
348 | m_lineHeaderData[index].length = qMax(a: 0.0f, b: m_length + m_lengthVariation * (system()->rand()->get(particleIndex: index) - 0.5f)); |
349 | return index; |
350 | } |
351 | |
352 | void QQuick3DParticleLineParticle::setParticleData(int particleIndex, |
353 | const QVector3D &position, |
354 | const QVector3D &rotation, |
355 | const QVector4D &color, |
356 | float size, float age, |
357 | float animationFrame) |
358 | { |
359 | auto &dst = m_spriteParticleData[particleIndex]; |
360 | bool update = size > 0.0f || dst.size > 0.0f; |
361 | QQuick3DParticleSpriteParticle::setParticleData(particleIndex, position, rotation, color, size, age, animationFrame); |
362 | if (update) |
363 | updateLineSegment(particleIndex); |
364 | } |
365 | |
366 | void QQuick3DParticleLineParticle::resetParticleData(int particleIndex) |
367 | { |
368 | LineDataHeader * = m_lineHeaderData.data() + particleIndex; |
369 | if (header->pointCount) { |
370 | header->currentIndex = 0; |
371 | header->pointCount = 0; |
372 | } |
373 | QQuick3DParticleSpriteParticle::resetParticleData(particleIndex); |
374 | } |
375 | |
376 | void QQuick3DParticleLineParticle::saveLineSegment(int particleIndex, float time) |
377 | { |
378 | if (m_eolFadeOutDuration > 0 && m_lineHeaderData[particleIndex].pointCount > 0) { |
379 | FadeOutLineData data; |
380 | data.endPoint = m_spriteParticleData[particleIndex]; |
381 | data.beginTime = time; |
382 | data.endTime = time + m_eolFadeOutDuration * 0.001f; |
383 | data.timeFactor = 1000.0f / ((float)m_eolFadeOutDuration); |
384 | data.header = m_lineHeaderData[particleIndex]; |
385 | data.lineData = m_lineData.mid(pos: particleIndex * m_segmentCount, len: m_segmentCount); |
386 | data.emitterIndex = m_spriteParticleData[particleIndex].emitterIndex; |
387 | m_fadeOutData.emplaceBack(args&: data); |
388 | clearSegment(particleIndex); |
389 | } |
390 | } |
391 | |
392 | static QVector3D qt_normalFromRotation(const QVector3D &eulerRotation) |
393 | { |
394 | float x = qDegreesToRadians(degrees: eulerRotation.x()); |
395 | float y = qDegreesToRadians(degrees: eulerRotation.y()); |
396 | if (qFuzzyIsNull(f: x) && qFuzzyIsNull(f: y)) |
397 | return QVector3D(0, 0, -1); |
398 | float a = qCos(v: x); |
399 | float b = qSin(v: x); |
400 | float c = qCos(v: y); |
401 | float d = qSin(v: y); |
402 | return QVector3D(d, -b * c, a * c); |
403 | } |
404 | |
405 | void QQuick3DParticleLineParticle::updateLineBuffer(LineParticleUpdateNode *updateNode, QSSGRenderGraphObject *spatialNode) |
406 | { |
407 | const auto &perEmitter = perEmitterData(updateNode); |
408 | QSSGRenderParticles *node = static_cast<QSSGRenderParticles *>(spatialNode); |
409 | if (!node) |
410 | return; |
411 | |
412 | int lineCount = 0; |
413 | for (int i = 0; i < m_lineHeaderData.size(); i++) { |
414 | if (m_lineHeaderData[i].pointCount && m_lineHeaderData[i].emitterIndex == perEmitter.emitterIndex) |
415 | lineCount++; |
416 | } |
417 | int totalCount = lineCount; |
418 | if (m_perEmitterData.size() > 1) { |
419 | for (int i = 0; i < m_fadeOutData.size(); i++) { |
420 | if (m_fadeOutData[i].emitterIndex == perEmitter.emitterIndex) |
421 | totalCount++; |
422 | } |
423 | } else { |
424 | totalCount += m_fadeOutData.size(); |
425 | } |
426 | |
427 | if (node->m_particleBuffer.particleCount() != totalCount) |
428 | node->m_particleBuffer.resizeLine(particleCount: totalCount, segmentCount: m_segmentCount + 1); |
429 | |
430 | if (!totalCount) return; |
431 | |
432 | const int segments = m_segmentCount; |
433 | const int particlesPerSlice = node->m_particleBuffer.particlesPerSlice(); |
434 | const int sliceStride = node->m_particleBuffer.sliceStride(); |
435 | int sliceParticleIdx = 0; |
436 | int slice = 0; |
437 | char *dest = node->m_particleBuffer.pointer(); |
438 | QSSGBounds3 bounds; |
439 | |
440 | const LineDataHeader * = m_lineHeaderData.constData(); |
441 | const LineData *lineData = m_lineData.constData(); |
442 | const SpriteParticleData *src = m_spriteParticleData.constData(); |
443 | |
444 | auto nextParticle = [](char *&buffer, int &slice, int &sliceParticleIdx, int particlesPerSlice, int sliceStride) -> QSSGLineParticle* { |
445 | QSSGLineParticle *ret = reinterpret_cast<QSSGLineParticle *>(buffer) + sliceParticleIdx; |
446 | sliceParticleIdx++; |
447 | if (sliceParticleIdx == particlesPerSlice) { |
448 | slice++; |
449 | buffer += sliceStride; |
450 | sliceParticleIdx = 0; |
451 | } |
452 | return ret; |
453 | }; |
454 | |
455 | auto genLine = [&](const SpriteParticleData &sdata, const LineDataHeader &, const LineData *tdata, |
456 | QSSGBounds3 &bounds, int segments, float particleScale, float alpha, |
457 | char *&buffer, int &slice, int &sliceParticleIdx, int particlesPerSlice, int sliceStride, bool absolute, bool fill) { |
458 | QSSGLineParticle *particle = nextParticle(buffer, slice, sliceParticleIdx, particlesPerSlice, sliceStride); |
459 | int idx = header.currentIndex; |
460 | particle->color = sdata.color; |
461 | particle->color.setW(sdata.color.w() * alpha); |
462 | QVector3D tangent = (tdata[idx].position - sdata.position).normalized(); |
463 | QVector3D binormal = QVector3D::crossProduct(v1: qt_normalFromRotation(eulerRotation: sdata.rotation), v2: tangent); |
464 | particle->binormal = binormal; |
465 | particle->position = sdata.position; |
466 | particle->age = sdata.age; |
467 | particle->animationFrame = sdata.animationFrame; |
468 | particle->size = sdata.size * particleScale; |
469 | float partialLength = (tdata[idx].position - sdata.position).length(); |
470 | float length0 = tdata[idx].length + partialLength; |
471 | particle->length = 0.0f; |
472 | float lineLength = header.length; |
473 | int lastIdx = (idx + 1 + segments - header.pointCount) % segments; |
474 | float lengthScale = -1.0f; |
475 | |
476 | if (absolute) { |
477 | particle->length = length0; |
478 | length0 = 0; |
479 | } |
480 | |
481 | if (fill) { |
482 | if (lineLength > 0.0f) { |
483 | lengthScale = -1.0f / lineLength; |
484 | } else { |
485 | float totalLength = tdata[idx].length - tdata[lastIdx].length; |
486 | if (header.pointCount < segments) |
487 | totalLength += partialLength; |
488 | if (!qFuzzyIsNull(f: totalLength)) |
489 | lengthScale = -1.0f / totalLength; |
490 | } |
491 | } |
492 | bounds.include(v: sdata.position); |
493 | |
494 | QSSGLineParticle *prevGood = particle; |
495 | int segmentIdx = 0; |
496 | int prevIdx = 0; |
497 | Q_ASSERT(header.pointCount <= m_segmentCount); |
498 | |
499 | if (header.length >= 0.0f) { |
500 | float totalLength = 0; |
501 | float prevLength = tdata[idx].length + partialLength; |
502 | for (segmentIdx = 0; segmentIdx < header.pointCount && totalLength < header.length; segmentIdx++) { |
503 | particle = nextParticle(buffer, slice, sliceParticleIdx, particlesPerSlice, sliceStride); |
504 | particle->size = tdata[idx].size * particleScale; |
505 | if (particle->size > 0.0f) { |
506 | bounds.include(v: tdata[idx].position); |
507 | particle->color = tdata[idx].color; |
508 | particle->color.setW(tdata[idx].color.w() * alpha); |
509 | particle->binormal = tdata[idx].binormal; |
510 | particle->position = tdata[idx].position; |
511 | particle->animationFrame = sdata.animationFrame; |
512 | particle->age = sdata.age; |
513 | particle->length = (length0 - tdata[idx].length) * lengthScale; |
514 | float segmentLength = prevLength - tdata[idx].length; |
515 | prevLength = tdata[idx].length; |
516 | if (totalLength + segmentLength > header.length) { |
517 | float diff = totalLength + segmentLength - header.length; |
518 | particle->position -= tdata[idx].tangent * diff; |
519 | particle->length -= diff * lengthScale; |
520 | segmentLength -= diff; |
521 | } |
522 | totalLength += segmentLength; |
523 | prevGood = particle; |
524 | prevIdx = idx; |
525 | } |
526 | idx = idx ? (idx - 1) : (segments - 1); |
527 | } |
528 | } else { |
529 | for (segmentIdx = 0; segmentIdx < header.pointCount; segmentIdx++) { |
530 | particle = nextParticle(buffer, slice, sliceParticleIdx, particlesPerSlice, sliceStride); |
531 | particle->size = tdata[idx].size * particleScale; |
532 | if (particle->size > 0.0f) { |
533 | bounds.include(v: tdata[idx].position); |
534 | particle->color = tdata[idx].color; |
535 | particle->color.setW(tdata[idx].color.w() * alpha); |
536 | particle->binormal = tdata[idx].binormal; |
537 | particle->position = tdata[idx].position; |
538 | particle->animationFrame = sdata.animationFrame; |
539 | particle->age = sdata.age; |
540 | particle->length = (length0 - tdata[idx].length) * lengthScale; |
541 | prevGood = particle; |
542 | prevIdx = idx; |
543 | } |
544 | idx = idx ? (idx - 1) : (segments - 1); |
545 | } |
546 | } |
547 | for (;segmentIdx < segments; segmentIdx++) { |
548 | particle = nextParticle(buffer, slice, sliceParticleIdx, particlesPerSlice, sliceStride); |
549 | *particle = *prevGood; |
550 | particle->size = 0.0f; |
551 | particle->length = 0.0f; |
552 | idx = idx ? (idx - 1) : (segments - 1); |
553 | } |
554 | // Do only for full segment |
555 | if (prevGood == particle && header.length < 0.0f && segments > 1) { |
556 | prevGood->position -= tdata[prevIdx].tangent * partialLength; |
557 | if (!fill) |
558 | prevGood->length -= partialLength * lengthScale; |
559 | } |
560 | }; |
561 | |
562 | const bool absolute = m_texcoordMode == TexcoordMode::Absolute; |
563 | const bool fill = m_texcoordMode == TexcoordMode::Fill; |
564 | int i = 0; |
565 | while (i < lineCount) { |
566 | if (header->pointCount && header->emitterIndex == perEmitter.emitterIndex) { |
567 | genLine(*src, *header, lineData, bounds, segments, particleScale(), 1.0f, dest, |
568 | slice, sliceParticleIdx, particlesPerSlice, sliceStride, absolute, fill); |
569 | i++; |
570 | } |
571 | header++; |
572 | lineData += segments; |
573 | src++; |
574 | } |
575 | |
576 | float time = system()->currentTime() * 0.001f; |
577 | for (const FadeOutLineData &fdata : m_fadeOutData) { |
578 | if (fdata.emitterIndex == perEmitter.emitterIndex) { |
579 | float factor = 1.0f - (time - fdata.beginTime) * fdata.timeFactor; |
580 | genLine(fdata.endPoint, fdata.header, fdata.lineData.data(), bounds, segments, |
581 | particleScale(), factor, dest, slice, sliceParticleIdx, particlesPerSlice, |
582 | sliceStride, absolute, fill); |
583 | } |
584 | } |
585 | node->m_particleBuffer.setBounds(bounds); |
586 | } |
587 | |
588 | void QQuick3DParticleLineParticle::handleSegmentCountChanged() |
589 | { |
590 | markNodesDirty(); |
591 | m_lineData.resize(size: m_segmentCount * m_maxAmount); |
592 | m_lineData.fill(t: {}); |
593 | m_lineHeaderData.resize(size: m_maxAmount); |
594 | m_lineHeaderData.fill(t: {}); |
595 | m_fadeOutData.clear(); |
596 | if (!m_spriteParticleData.isEmpty()) { |
597 | auto count = qMin(a: m_maxAmount, b: m_spriteParticleData.size()); |
598 | for (int i = 0; i < count; i++) |
599 | m_lineHeaderData[i].emitterIndex = m_spriteParticleData[i].emitterIndex; |
600 | } |
601 | } |
602 | |
603 | void QQuick3DParticleLineParticle::updateLineSegment(int particleIndex) |
604 | { |
605 | if (m_lineData.isEmpty()) { |
606 | qWarning () << "Line particle updated before having been initialized" ; |
607 | return; |
608 | } |
609 | LineDataHeader * = m_lineHeaderData.data() + particleIndex; |
610 | int idx = header->currentIndex; |
611 | LineData *cur = m_lineData.data() + particleIndex * m_segmentCount; |
612 | LineData *prev = header->pointCount ? cur + idx : nullptr; |
613 | const SpriteParticleData &src = m_spriteParticleData.at(i: particleIndex); |
614 | |
615 | if (prev && m_segmentCount > 1) { |
616 | float length = (prev->position - src.position).length(); |
617 | float minLength = m_lengthDeltaMin; |
618 | if (header->length >= 0.0f) |
619 | minLength = header->length / (m_segmentCount - 1); |
620 | if (length < minLength) |
621 | return; |
622 | } |
623 | |
624 | if (header->pointCount < m_segmentCount) |
625 | header->pointCount++; |
626 | |
627 | if (prev) |
628 | idx = (idx + 1) % m_segmentCount; |
629 | header->currentIndex = idx; |
630 | cur = cur + idx; |
631 | |
632 | cur->color = src.color; |
633 | cur->size = src.size; |
634 | cur->normal = qt_normalFromRotation(eulerRotation: src.rotation); |
635 | |
636 | if (prev && m_segmentCount == 1) { |
637 | QVector3D t = prev->position - src.position; |
638 | float l = t.length(); |
639 | t.normalize(); |
640 | float minLength = m_lengthDeltaMin; |
641 | if (header->length >= 0.0f) |
642 | minLength = header->length; |
643 | cur->position = src.position + minLength * t; |
644 | cur->length += l; |
645 | cur->tangent = t; |
646 | cur->binormal = QVector3D::crossProduct(v1: cur->normal, v2: t); |
647 | } else { |
648 | cur->position = src.position; |
649 | cur->length = 0.0f; |
650 | } |
651 | |
652 | if (prev && prev != cur) { |
653 | prev->tangent = prev->position - src.position; |
654 | cur->length = prev->tangent.length(); |
655 | prev->tangent /= cur->length; |
656 | cur->length += prev->length; |
657 | cur->binormal = QVector3D::crossProduct(v1: cur->normal, v2: prev->tangent); |
658 | if (header->pointCount == 1) |
659 | prev->binormal = cur->binormal; |
660 | else |
661 | prev->binormal = (prev->binormal + cur->binormal).normalized(); |
662 | } |
663 | } |
664 | |
665 | void QQuick3DParticleLineParticle::clearSegment(int particleIndex) |
666 | { |
667 | if (m_lineData.isEmpty()) |
668 | return; |
669 | LineDataHeader * = m_lineHeaderData.data() + particleIndex; |
670 | if (header->pointCount) { |
671 | auto data = m_lineData.begin() + particleIndex * m_segmentCount; |
672 | std::fill_n(first: data, n: m_segmentCount, value: LineData()); |
673 | } |
674 | header->emitterIndex = -1; |
675 | header->currentIndex = 0; |
676 | header->pointCount = 0; |
677 | header->length = -1.0f; |
678 | } |
679 | |
680 | QT_END_NAMESPACE |
681 | |