| 1 | // Copyright (C) 2021 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
| 3 | |
| 4 | #include "qquick3dparticlespritesequence_p.h" |
| 5 | #include "qquick3dparticlespriteparticle_p.h" |
| 6 | |
| 7 | QT_BEGIN_NAMESPACE |
| 8 | |
| 9 | /*! |
| 10 | \qmltype SpriteSequence3D |
| 11 | \inherits QtObject |
| 12 | \inqmlmodule QtQuick3D.Particles3D |
| 13 | \brief Provides image sequence features for the Sprite particles. |
| 14 | \since 6.2 |
| 15 | |
| 16 | The SpriteSequence3D element provides support for animated images with multiple frames. The frames |
| 17 | should be aligned horizontally in the image, first frame being on the left and last on the right. |
| 18 | |
| 19 | To make a \l SpriteParticle3D an animated sequence, set its \l {SpriteParticle3D::spriteSequence}{spriteSequence} property. |
| 20 | */ |
| 21 | |
| 22 | QQuick3DParticleSpriteSequence::QQuick3DParticleSpriteSequence(QObject *parent) |
| 23 | : QObject(parent) |
| 24 | { |
| 25 | |
| 26 | } |
| 27 | |
| 28 | QQuick3DParticleSpriteSequence::~QQuick3DParticleSpriteSequence() |
| 29 | { |
| 30 | if (m_parentParticle) |
| 31 | m_parentParticle->setSpriteSequence(nullptr); |
| 32 | } |
| 33 | |
| 34 | /*! |
| 35 | \qmlproperty int SpriteSequence3D::frameCount |
| 36 | |
| 37 | This property defines the amount of image frames in \l {SpriteParticle3D::}{sprite}. |
| 38 | Particle animates through these frames during its \l duration. |
| 39 | The frames should be laid out horizontally in the same image file. For example, |
| 40 | \e sprite could be a \c {512x64} image, with \c frameCount of \c 8. This would make |
| 41 | each particle frame size \c {64x64} pixels. |
| 42 | |
| 43 | The default value is \c 1. |
| 44 | |
| 45 | \note If your image only has a single sprite frame, don't define the |
| 46 | \l {SpriteParticle3D::}{spriteSequence} property at all. |
| 47 | |
| 48 | \sa interpolate |
| 49 | */ |
| 50 | int QQuick3DParticleSpriteSequence::frameCount() const |
| 51 | { |
| 52 | return m_frameCount; |
| 53 | } |
| 54 | |
| 55 | /*! |
| 56 | \qmlproperty int SpriteSequence3D::frameIndex |
| 57 | |
| 58 | This property defines the initial index of the frame. This is the position in between frames |
| 59 | where the animation is started. For example when the \c frameIndex is 5 and the \l animationDirection |
| 60 | is \c Normal, the first rendered frame is 5. If the \l animationDirection is \c Reverse, the |
| 61 | first rendered frame is 4. |
| 62 | |
| 63 | The value of frameIndex must be between 0 and \l{frameCount} - \c 1. When the \l animationDirection |
| 64 | is \c SingleFrame and \l randomStart is \c false, all the particles will render sprites with the |
| 65 | \c frameIndex. |
| 66 | |
| 67 | The default value is \c 0. |
| 68 | |
| 69 | \sa randomStart, animationDirection |
| 70 | */ |
| 71 | int QQuick3DParticleSpriteSequence::frameIndex() const |
| 72 | { |
| 73 | return m_frameIndex; |
| 74 | } |
| 75 | |
| 76 | /*! |
| 77 | \qmlproperty bool SpriteSequence3D::interpolate |
| 78 | |
| 79 | This property defines if the sprites are interpolated (blended) between frames |
| 80 | to make the animation appear smoother. |
| 81 | |
| 82 | The default value is \c true. |
| 83 | |
| 84 | \sa frameCount |
| 85 | */ |
| 86 | bool QQuick3DParticleSpriteSequence::interpolate() const |
| 87 | { |
| 88 | return m_interpolate; |
| 89 | } |
| 90 | |
| 91 | /*! |
| 92 | \qmlproperty int SpriteSequence3D::duration |
| 93 | |
| 94 | This property defines the duration in milliseconds how long it takes for the |
| 95 | sprite sequence to animate. For example, if the \l duration is \c 400 and the |
| 96 | \l frameCount is 8, each frame will be shown for 50 milliseconds. When the |
| 97 | value is -1, the particle lifeSpan is used as the duration. |
| 98 | |
| 99 | The default value is \c -1. |
| 100 | */ |
| 101 | int QQuick3DParticleSpriteSequence::duration() const |
| 102 | { |
| 103 | return m_duration; |
| 104 | } |
| 105 | |
| 106 | /*! |
| 107 | \qmlproperty int SpriteSequence3D::durationVariation |
| 108 | |
| 109 | This property defines the duration variation in milliseconds. The actual duration |
| 110 | of the animation is between \c duration - \c durationVariation and \c duration + |
| 111 | \c durationVariation. |
| 112 | |
| 113 | The default value is \c 0 (no variation). |
| 114 | */ |
| 115 | int QQuick3DParticleSpriteSequence::durationVariation() const |
| 116 | { |
| 117 | return m_durationVariation; |
| 118 | } |
| 119 | |
| 120 | /*! |
| 121 | \qmlproperty bool SpriteSequence3D::randomStart |
| 122 | |
| 123 | This property defines if the animation should start from a random frame between \c 0 and \l frameCount - \c 1. |
| 124 | This allows animations to not look like they all just started when the animation begins. |
| 125 | |
| 126 | The default value is \c false. |
| 127 | |
| 128 | \sa animationDirection |
| 129 | */ |
| 130 | bool QQuick3DParticleSpriteSequence::randomStart() const |
| 131 | { |
| 132 | return m_randomStart; |
| 133 | } |
| 134 | |
| 135 | /*! |
| 136 | \qmlproperty AnimationDirection SpriteSequence3D::animationDirection |
| 137 | |
| 138 | This property defines the animation direction of the sequence. |
| 139 | |
| 140 | The default value is \c SpriteSequence3D.Normal. |
| 141 | |
| 142 | \sa randomStart |
| 143 | */ |
| 144 | |
| 145 | /*! |
| 146 | \qmlproperty enumeration SpriteSequence3D::AnimationDirection |
| 147 | |
| 148 | Defines the animation playback direction of the sequence. |
| 149 | |
| 150 | \value SpriteSequence3D.Normal |
| 151 | Animate from the first frame to the last frame. When the last frame is reached, jump back to the first frame. |
| 152 | \value SpriteSequence3D.Reverse |
| 153 | Animate from the last frame to the first frame. When the first frame is reached, jump back to the last frame. |
| 154 | \value SpriteSequence3D.Alternate |
| 155 | Animate from the first frame to the last frame. When the last or first frame is reached, switch the animation direction. |
| 156 | This makes the sequence animation smooth even when the first and the last frames don't match. |
| 157 | \value SpriteSequence3D.AlternateReverse |
| 158 | Animate from the last frame to the first frame. When the last or first frame is reached, switch the animation direction. |
| 159 | This makes the sequence animation smooth even when the first and the last frames don't match. |
| 160 | \value SpriteSequence3D.SingleFrame |
| 161 | Don't animate the frame. When the \l randomStart is false, \l frameIndex frame is rendered. |
| 162 | When the \l randomStart is true, each particle renders a random frame. |
| 163 | */ |
| 164 | QQuick3DParticleSpriteSequence::AnimationDirection QQuick3DParticleSpriteSequence::animationDirection() const |
| 165 | { |
| 166 | return m_animationDirection; |
| 167 | } |
| 168 | |
| 169 | void QQuick3DParticleSpriteSequence::setFrameCount(int frameCount) |
| 170 | { |
| 171 | if (m_frameCount == frameCount) |
| 172 | return; |
| 173 | m_frameCount = std::max(a: 1, b: frameCount); |
| 174 | markNodesDirty(); |
| 175 | Q_EMIT frameCountChanged(); |
| 176 | } |
| 177 | |
| 178 | void QQuick3DParticleSpriteSequence::setFrameIndex(int frameIndex) |
| 179 | { |
| 180 | if (m_frameIndex == frameIndex) |
| 181 | return; |
| 182 | m_frameIndex = std::max(a: 0, b: frameIndex); |
| 183 | markNodesDirty(); |
| 184 | Q_EMIT frameIndexChanged(); |
| 185 | } |
| 186 | |
| 187 | void QQuick3DParticleSpriteSequence::setInterpolate(bool interpolate) |
| 188 | { |
| 189 | if (m_interpolate == interpolate) |
| 190 | return; |
| 191 | m_interpolate = interpolate; |
| 192 | markNodesDirty(); |
| 193 | Q_EMIT interpolateChanged(); |
| 194 | } |
| 195 | |
| 196 | void QQuick3DParticleSpriteSequence::setDuration(int duration) |
| 197 | { |
| 198 | if (m_duration == duration) |
| 199 | return; |
| 200 | |
| 201 | m_duration = duration; |
| 202 | markNodesDirty(); |
| 203 | Q_EMIT durationChanged(); |
| 204 | } |
| 205 | |
| 206 | void QQuick3DParticleSpriteSequence::setDurationVariation(int durationVariation) |
| 207 | { |
| 208 | if (m_durationVariation == durationVariation) |
| 209 | return; |
| 210 | |
| 211 | m_durationVariation = durationVariation; |
| 212 | markNodesDirty(); |
| 213 | Q_EMIT durationVariationChanged(); |
| 214 | } |
| 215 | |
| 216 | void QQuick3DParticleSpriteSequence::setRandomStart(bool randomStart) |
| 217 | { |
| 218 | if (m_randomStart == randomStart) |
| 219 | return; |
| 220 | m_randomStart = randomStart; |
| 221 | markNodesDirty(); |
| 222 | Q_EMIT randomStartChanged(); |
| 223 | } |
| 224 | |
| 225 | void QQuick3DParticleSpriteSequence::setAnimationDirection(QQuick3DParticleSpriteSequence::AnimationDirection animationDirection) |
| 226 | { |
| 227 | if (m_animationDirection == animationDirection) |
| 228 | return; |
| 229 | m_animationDirection = animationDirection; |
| 230 | markNodesDirty(); |
| 231 | Q_EMIT animationDirectionChanged(); |
| 232 | } |
| 233 | |
| 234 | void QQuick3DParticleSpriteSequence::componentComplete() |
| 235 | { |
| 236 | m_parentParticle = qobject_cast<QQuick3DParticleSpriteParticle *>(object: parent()); |
| 237 | if (!m_parentParticle) |
| 238 | qWarning() << "SpriteSequence3D requires parent SpriteParticle3D to function correctly!" ; |
| 239 | } |
| 240 | |
| 241 | void QQuick3DParticleSpriteSequence::markNodesDirty() |
| 242 | { |
| 243 | if (m_parentParticle) |
| 244 | m_parentParticle->markNodesDirty(); |
| 245 | } |
| 246 | |
| 247 | // Returns the first frame of the sequence. |
| 248 | // Return range [0..1) where 0.0 is the first frame and 0.9999 is the last. |
| 249 | float QQuick3DParticleSpriteSequence::firstFrame(int index, bool singleFrame) |
| 250 | { |
| 251 | float firstFrame = 0.0f; |
| 252 | if (m_randomStart) { |
| 253 | if (!m_parentParticle || !m_parentParticle->m_system) |
| 254 | return firstFrame; |
| 255 | auto rand = m_parentParticle->m_system->rand(); |
| 256 | firstFrame = rand->get(particleIndex: index, user: QPRand::SpriteAnimationI); |
| 257 | } else if (m_frameCount > 1 && m_frameIndex > 0) { |
| 258 | int frameIndex = std::min(a: m_frameIndex, b: m_frameCount - 1); |
| 259 | if (singleFrame) |
| 260 | firstFrame = float(frameIndex) / (float(m_frameCount - 1) + 0.0001f); |
| 261 | else |
| 262 | firstFrame = float(frameIndex) / float(m_frameCount); |
| 263 | } |
| 264 | return firstFrame; |
| 265 | } |
| 266 | |
| 267 | QT_END_NAMESPACE |
| 268 | |