1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtCore/qmath.h> |
5 | #include "qquick3dparticlewander_p.h" |
6 | #include "qquick3dparticlerandomizer_p.h" |
7 | #include "qquick3dparticleutils_p.h" |
8 | |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | /*! |
12 | \qmltype Wander3D |
13 | \inherits Affector3D |
14 | \inqmlmodule QtQuick3D.Particles3D |
15 | \brief Applies random wave curves to particles. |
16 | \since 6.2 |
17 | |
18 | This element applies random wave curves to particles. Curves can combine |
19 | global values which are the same for all particles and unique values which |
20 | differ randomly. |
21 | */ |
22 | |
23 | QQuick3DParticleWander::QQuick3DParticleWander(QQuick3DNode *parent) |
24 | : QQuick3DParticleAffector(parent) |
25 | { |
26 | } |
27 | |
28 | /*! |
29 | \qmlproperty vector3d Wander3D::globalAmount |
30 | |
31 | This property defines how long distance each particle moves at the ends of curves. |
32 | So if the value is for example (100, 10, 0), all particles wander between (100, 10, 0) |
33 | and (-100, -10, 0). |
34 | |
35 | The default value is \c (0.0, 0.0, 0.0). |
36 | */ |
37 | const QVector3D &QQuick3DParticleWander::globalAmount() const |
38 | { |
39 | return m_globalAmount; |
40 | } |
41 | |
42 | /*! |
43 | \qmlproperty vector3d Wander3D::globalPace |
44 | |
45 | This property defines the pace (frequency) each particle wanders in curves per second. |
46 | |
47 | The default value is \c (0.0, 0.0, 0.0). |
48 | */ |
49 | const QVector3D &QQuick3DParticleWander::globalPace() const |
50 | { |
51 | return m_globalPace; |
52 | } |
53 | |
54 | /*! |
55 | \qmlproperty vector3d Wander3D::globalPaceStart |
56 | |
57 | This property defines the starting point for the pace (frequency). The meaningful range |
58 | is between 0 .. 2 * PI. For example, to animate the x-coordinate of the pace start: |
59 | |
60 | \qml |
61 | PropertyAnimation on globalPaceStart { |
62 | loops: Animation.Infinite |
63 | duration: 2000 |
64 | from: Qt.vector3d(0, 0, 0) |
65 | to: Qt.vector3d(Math.PI * 2, 0, 0) |
66 | } |
67 | \endqml |
68 | |
69 | The default value is \c (0.0, 0.0, 0.0). |
70 | */ |
71 | const QVector3D &QQuick3DParticleWander::globalPaceStart() const |
72 | { |
73 | return m_globalPaceStart; |
74 | } |
75 | |
76 | /*! |
77 | \qmlproperty vector3d Wander3D::uniqueAmount |
78 | |
79 | This property defines how long distance each particle moves at the ends of curves at maximum. |
80 | |
81 | The default value is \c (0.0, 0.0, 0.0). |
82 | */ |
83 | const QVector3D &QQuick3DParticleWander::uniqueAmount() const |
84 | { |
85 | return m_uniqueAmount; |
86 | } |
87 | |
88 | /*! |
89 | \qmlproperty vector3d Wander3D::uniquePace |
90 | |
91 | This property defines the unique pace (frequency) each particle wanders in curves per second. |
92 | |
93 | The default value is \c (0.0, 0.0, 0.0). |
94 | */ |
95 | const QVector3D &QQuick3DParticleWander::uniquePace() const |
96 | { |
97 | return m_uniquePace; |
98 | } |
99 | |
100 | /*! |
101 | \qmlproperty real Wander3D::uniqueAmountVariation |
102 | |
103 | This property defines variation for \l uniqueAmount between 0.0 and 1.0. |
104 | When the amount variation is 0.0, every particle reaches maximum amount. When it's 0.5, |
105 | every particle reaches between 0.5 - 1.5 of the amount. |
106 | For example if \l uniqueAmount is (100, 50, 20) and \l uniqueAmountVariation is 0.1, |
107 | the particles maximum wave distances are something random between (110, 55, 22) |
108 | and (90, 45, 18). |
109 | |
110 | The default value is \c 0.0. |
111 | */ |
112 | float QQuick3DParticleWander::uniqueAmountVariation() const |
113 | { |
114 | return m_uniqueAmountVariation; |
115 | } |
116 | |
117 | /*! |
118 | \qmlproperty real Wander3D::uniquePaceVariation |
119 | |
120 | This property defines the unique pace (frequency) variation for each particle |
121 | between 0.0 and 1.0. When the variation is 0.0, every particle wander at the same |
122 | frequency. For example if \l uniquePace is (1.0, 2.0, 4.0) and \l uniquePaceVariation |
123 | is 0.5, the particles wave paces are something random between (2.0, 4.0, 8.0) |
124 | and (0.5, 1.0, 2.0). |
125 | |
126 | The default value is \c 0.0. |
127 | */ |
128 | float QQuick3DParticleWander::uniquePaceVariation() const |
129 | { |
130 | return m_uniquePaceVariation; |
131 | } |
132 | |
133 | /*! |
134 | \qmlproperty int Wander3D::fadeInDuration |
135 | |
136 | This property defines the duration in milliseconds for fading in the affector. |
137 | After this duration, the wandering will be in full effect. |
138 | Setting this can be useful to emit from a specific position or shape, |
139 | otherwise wander will affect position also at the beginning. |
140 | |
141 | The default value is \c 0. |
142 | */ |
143 | int QQuick3DParticleWander::fadeInDuration() const |
144 | { |
145 | return m_fadeInDuration; |
146 | } |
147 | |
148 | /*! |
149 | \qmlproperty int Wander3D::fadeOutDuration |
150 | |
151 | This property defines the duration in milliseconds for fading out the affector. |
152 | Setting this can be useful to reduce the wander when the particle life |
153 | time ends, for example when combined with \l Attractor3D so end positions will |
154 | match the \l{Attractor3D::shape}{shape}. |
155 | |
156 | The default value is \c 0. |
157 | */ |
158 | int QQuick3DParticleWander::fadeOutDuration() const |
159 | { |
160 | return m_fadeOutDuration; |
161 | } |
162 | |
163 | void QQuick3DParticleWander::setGlobalAmount(const QVector3D &globalAmount) |
164 | { |
165 | if (m_globalAmount == globalAmount) |
166 | return; |
167 | |
168 | m_globalAmount = globalAmount; |
169 | Q_EMIT globalAmountChanged(); |
170 | Q_EMIT update(); |
171 | } |
172 | |
173 | void QQuick3DParticleWander::setGlobalPace(const QVector3D &globalPace) |
174 | { |
175 | if (m_globalPace == globalPace) |
176 | return; |
177 | |
178 | m_globalPace = globalPace; |
179 | Q_EMIT globalPaceChanged(); |
180 | Q_EMIT update(); |
181 | } |
182 | |
183 | void QQuick3DParticleWander::setGlobalPaceStart(const QVector3D &globalPaceStart) |
184 | { |
185 | if (m_globalPaceStart == globalPaceStart) |
186 | return; |
187 | |
188 | m_globalPaceStart = globalPaceStart; |
189 | Q_EMIT globalPaceStartChanged(); |
190 | Q_EMIT update(); |
191 | } |
192 | |
193 | void QQuick3DParticleWander::setUniqueAmount(const QVector3D &uniqueAmount) |
194 | { |
195 | if (m_uniqueAmount == uniqueAmount) |
196 | return; |
197 | |
198 | m_uniqueAmount = uniqueAmount; |
199 | Q_EMIT uniqueAmountChanged(); |
200 | Q_EMIT update(); |
201 | } |
202 | |
203 | void QQuick3DParticleWander::setUniquePace(const QVector3D &uniquePace) |
204 | { |
205 | if (m_uniquePace == uniquePace) |
206 | return; |
207 | |
208 | m_uniquePace = uniquePace; |
209 | Q_EMIT uniquePaceChanged(); |
210 | Q_EMIT update(); |
211 | } |
212 | |
213 | void QQuick3DParticleWander::setUniqueAmountVariation(float uniqueAmountVariation) |
214 | { |
215 | if (qFuzzyCompare(p1: m_uniqueAmountVariation, p2: uniqueAmountVariation)) |
216 | return; |
217 | |
218 | uniqueAmountVariation = std::max(a: 0.0f, b: std::min(a: 1.0f, b: uniqueAmountVariation)); |
219 | m_uniqueAmountVariation = uniqueAmountVariation; |
220 | Q_EMIT uniqueAmountVariationChanged(); |
221 | Q_EMIT update(); |
222 | } |
223 | |
224 | void QQuick3DParticleWander::setUniquePaceVariation(float uniquePaceVariation) |
225 | { |
226 | if (qFuzzyCompare(p1: m_uniquePaceVariation, p2: uniquePaceVariation)) |
227 | return; |
228 | |
229 | uniquePaceVariation = std::max(a: 0.0f, b: std::min(a: 1.0f, b: uniquePaceVariation)); |
230 | m_uniquePaceVariation = uniquePaceVariation; |
231 | Q_EMIT uniquePaceVariationChanged(); |
232 | Q_EMIT update(); |
233 | } |
234 | |
235 | void QQuick3DParticleWander::setFadeInDuration(int fadeInDuration) |
236 | { |
237 | if (m_fadeInDuration == fadeInDuration) |
238 | return; |
239 | |
240 | m_fadeInDuration = std::max(a: 0, b: fadeInDuration); |
241 | Q_EMIT fadeInDurationChanged(); |
242 | Q_EMIT update(); |
243 | } |
244 | |
245 | void QQuick3DParticleWander::setFadeOutDuration(int fadeOutDuration) |
246 | { |
247 | if (m_fadeOutDuration == fadeOutDuration) |
248 | return; |
249 | |
250 | m_fadeOutDuration = std::max(a: 0, b: fadeOutDuration); |
251 | Q_EMIT fadeOutDurationChanged(); |
252 | Q_EMIT update(); |
253 | } |
254 | |
255 | void QQuick3DParticleWander::affectParticle(const QQuick3DParticleData &sd, QQuick3DParticleDataCurrent *d, float time) |
256 | { |
257 | if (!system()) |
258 | return; |
259 | auto rand = system()->rand(); |
260 | |
261 | // Optionally smoothen the beginning & end of wander |
262 | float smooth = 1.0f; |
263 | if (m_fadeInDuration > 0) { |
264 | smooth = time / (float(m_fadeInDuration) / 1000.0f); |
265 | smooth = std::min(a: 1.0f, b: smooth); |
266 | } |
267 | if (m_fadeOutDuration > 0) { |
268 | float timeLeft = (sd.lifetime - time); |
269 | float smoothOut = timeLeft / (float(m_fadeOutDuration) / 1000.0f); |
270 | // When fading both in & out, select smaller (which is always max 1.0) |
271 | smooth = std::min(a: smoothOut, b: smooth); |
272 | } |
273 | |
274 | const float pi2 = float(M_PI * 2); |
275 | // Global |
276 | if (!qFuzzyIsNull(f: m_globalAmount.x()) && !qFuzzyIsNull(f: m_globalPace.x())) |
277 | d->position.setX(d->position.x() + smooth * QPSIN(x: m_globalPaceStart.x() + time * pi2 * m_globalPace.x()) * m_globalAmount.x()); |
278 | if (!qFuzzyIsNull(f: m_globalAmount.y()) && !qFuzzyIsNull(f: m_globalPace.y())) |
279 | d->position.setY(d->position.y() + smooth * QPSIN(x: m_globalPaceStart.y() + time * pi2 * m_globalPace.y()) * m_globalAmount.y()); |
280 | if (!qFuzzyIsNull(f: m_globalAmount.z()) && !qFuzzyIsNull(f: m_globalPace.z())) |
281 | d->position.setZ(d->position.z() + smooth * QPSIN(x: m_globalPaceStart.z() + time * pi2 * m_globalPace.z()) * m_globalAmount.z()); |
282 | |
283 | // Unique |
284 | // Rather simple to only use a single sin operation per direction |
285 | if (!qFuzzyIsNull(f: m_uniqueAmount.x()) && !qFuzzyIsNull(f: m_uniquePace.x())) { |
286 | // Values between 1.0 +/- variation |
287 | float paceVariation = 1.0f + m_uniquePaceVariation - 2.0f * rand->get(particleIndex: sd.index, user: QPRand::WanderXPV) * m_uniquePaceVariation; |
288 | float amountVariation = 1.0f + m_uniqueAmountVariation - 2.0f * rand->get(particleIndex: sd.index, user: QPRand::WanderXAV) * m_uniqueAmountVariation; |
289 | float startPace = rand->get(particleIndex: sd.index, user: QPRand::WanderXPS) * pi2; |
290 | float pace = startPace + paceVariation * time * pi2 * m_uniquePace.x(); |
291 | float amount = amountVariation * m_uniqueAmount.x(); |
292 | d->position.setX(d->position.x() + smooth * QPSIN(x: pace) * amount); |
293 | } |
294 | if (!qFuzzyIsNull(f: m_uniqueAmount.y()) && !qFuzzyIsNull(f: m_uniquePace.y())) { |
295 | // Values between 1.0 +/- variation |
296 | float paceVariation = 1.0f + m_uniquePaceVariation - 2.0f * rand->get(particleIndex: sd.index, user: QPRand::WanderYPV) * m_uniquePaceVariation; |
297 | float amountVariation = 1.0f + m_uniqueAmountVariation - 2.0f * rand->get(particleIndex: sd.index, user: QPRand::WanderYAV) * m_uniqueAmountVariation; |
298 | float startPace = rand->get(particleIndex: sd.index, user: QPRand::WanderYPS) * pi2; |
299 | float pace = startPace + paceVariation * time * pi2 * m_uniquePace.y(); |
300 | float amount = amountVariation * m_uniqueAmount.y(); |
301 | d->position.setY(d->position.y() + smooth * QPSIN(x: pace) * amount); |
302 | } |
303 | if (!qFuzzyIsNull(f: m_uniqueAmount.z()) && !qFuzzyIsNull(f: m_uniquePace.z())) { |
304 | // Values between 1.0 +/- variation |
305 | float paceVariation = 1.0f + m_uniquePaceVariation - 2.0f * rand->get(particleIndex: sd.index, user: QPRand::WanderZPV) * m_uniquePaceVariation; |
306 | float amountVariation = 1.0f + m_uniqueAmountVariation - 2.0f * rand->get(particleIndex: sd.index, user: QPRand::WanderZAV) * m_uniqueAmountVariation; |
307 | float startPace = rand->get(particleIndex: sd.index, user: QPRand::WanderZPS) * pi2; |
308 | float pace = startPace + paceVariation * time * pi2 * m_uniquePace.z(); |
309 | float amount = amountVariation * m_uniqueAmount.z(); |
310 | d->position.setZ(d->position.z() + smooth * QPSIN(x: pace) * amount); |
311 | } |
312 | } |
313 | |
314 | QT_END_NAMESPACE |
315 | |