| 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 | |