1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dparticleattractor_p.h"
5#include "qquick3dparticlerandomizer_p.h"
6#include "qquick3dparticleutils_p.h"
7
8QT_BEGIN_NAMESPACE
9
10/*!
11 \qmltype Attractor3D
12 \inherits Affector3D
13 \inqmlmodule QtQuick3D.Particles3D
14 \brief Attracts particles towards a position or a shape.
15 \since 6.2
16
17 This element attracts particles towards a position inside the 3D view. To model
18 the gravity of a massive object whose center of gravity is far away, use \l Gravity3D.
19
20 The attraction position is defined either with the \l {Node::position}{position} and
21 \l positionVariation or with \l shape. If both are defined, \l shape is used.
22*/
23
24// Minimum duration in seconds
25const float MIN_DURATION = 0.001f;
26
27QQuick3DParticleAttractor::QQuick3DParticleAttractor(QQuick3DNode *parent)
28 : QQuick3DParticleAffector(parent)
29 , m_duration(-1)
30 , m_durationVariation(0)
31{
32}
33
34/*!
35 \qmlproperty vector3d Attractor3D::positionVariation
36
37 This property defines the variation on attract position. It can be used to not attract
38 into a single point, but randomly towards a wider area. Here is an example how to attract
39 particles into some random point inside (50, 50, 50) cube at position (100, 0, 0) within
40 2 to 4 seconds:
41
42 \qml
43 Attractor3D {
44 position: Qt.vector3d(100, 0, 0)
45 positionVariation: Qt.vector3d(50, 50, 50)
46 duration: 3000
47 durationVariation: 1000
48 }
49 \endqml
50
51 The default value is \c (0, 0, 0) (no variation).
52
53 \sa {Node::position}, shape
54*/
55QVector3D QQuick3DParticleAttractor::positionVariation() const
56{
57 return m_positionVariation;
58}
59
60void QQuick3DParticleAttractor::setPositionVariation(const QVector3D &positionVariation)
61{
62 if (m_positionVariation == positionVariation)
63 return;
64
65 m_positionVariation = positionVariation;
66 Q_EMIT positionVariationChanged();
67 Q_EMIT update();
68}
69
70/*!
71 \qmlproperty ParticleAbstractShape3D Attractor3D::shape
72
73 This property defines a \l ParticleAbstractShape3D for particles attraction.
74 Each particle will be attracted into a random position inside this shape. This is an
75 alternative for defining \l {Node::position}{position} and \l positionVariation. Here
76 is an example how to attract particles into some random point inside sphere by the end
77 of the particles \l {ParticleEmitter3D::}{lifeSpan}:
78
79 \qml
80 Attractor3D {
81 position: Qt.vector3d(100, 0, 0)
82 shape: ParticleShape3D {
83 type: ParticleShape3D.Sphere
84 fill: true
85 }
86 }
87 \endqml
88
89 \sa {Node::position}, positionVariation
90*/
91QQuick3DParticleAbstractShape *QQuick3DParticleAttractor::shape() const
92{
93 return m_shape;
94}
95
96void QQuick3DParticleAttractor::setShape(QQuick3DParticleAbstractShape *shape)
97{
98 if (m_shape == shape)
99 return;
100
101 m_shape = shape;
102 m_shapeDirty = true;
103 Q_EMIT shapeChanged();
104 Q_EMIT update();
105}
106
107/*!
108 \qmlproperty int Attractor3D::duration
109
110 This property defines the duration in milliseconds how long it takes for particles to
111 reach the attaction position. When the value is -1, particle lifeSpan is used
112 as the duration.
113
114 The default value is \c -1.
115*/
116int QQuick3DParticleAttractor::duration() const
117{
118 return m_duration;
119}
120
121void QQuick3DParticleAttractor::setDuration(int duration)
122{
123 if (m_duration == duration)
124 return;
125
126 m_duration = duration;
127 Q_EMIT durationChanged();
128 Q_EMIT update();
129}
130
131/*!
132 \qmlproperty int Attractor3D::durationVariation
133
134 This property defines the duration variation in milliseconds. The actual duration to
135 reach attractor is between \c duration - \c durationVariation and \c duration + \c durationVariation.
136
137 The default value is \c 0 (no variation).
138*/
139int QQuick3DParticleAttractor::durationVariation() const
140{
141 return m_durationVariation;
142}
143
144void QQuick3DParticleAttractor::setDurationVariation(int durationVariation)
145{
146 if (m_durationVariation == durationVariation)
147 return;
148
149 m_durationVariation = durationVariation;
150 Q_EMIT durationVariationChanged();
151 Q_EMIT update();
152}
153
154/*!
155 \qmlproperty bool Attractor3D::hideAtEnd
156
157 This property defines if the particle should disappear when it reaches the attractor.
158
159 The default value is \c false.
160*/
161bool QQuick3DParticleAttractor::hideAtEnd() const
162{
163 return m_hideAtEnd;
164}
165
166/*!
167 \qmlproperty bool Attractor3D::useCachedPositions
168
169 This property defines if the attractor caches possible positions within its shape.
170 Cached positions give less random results but are better for performance.
171
172 The default value is \c true.
173*/
174bool QQuick3DParticleAttractor::useCachedPositions() const
175{
176 return m_useCachedPositions;
177}
178
179/*!
180 \qmlproperty int Attractor3D::positionsAmount
181
182 This property defines the amount of possible positions stored within the attractor shape.
183 By default the amount equals the particle count, but a lower amount can be used for a smaller cache.
184 Higher amount can be used for additional randomization.
185*/
186int QQuick3DParticleAttractor::positionsAmount() const
187{
188 return m_positionsAmount;
189}
190
191void QQuick3DParticleAttractor::setHideAtEnd(bool hideAtEnd)
192{
193 if (m_hideAtEnd == hideAtEnd)
194 return;
195
196 m_hideAtEnd = hideAtEnd;
197 Q_EMIT hideAtEndChanged();
198 Q_EMIT update();
199}
200
201void QQuick3DParticleAttractor::setUseCachedPositions(bool useCachedPositions)
202{
203 if (m_useCachedPositions == useCachedPositions)
204 return;
205
206 m_useCachedPositions = useCachedPositions;
207 Q_EMIT useCachedPositionsChanged();
208 m_shapeDirty = true;
209}
210
211void QQuick3DParticleAttractor::setPositionsAmount(int positionsAmount)
212{
213 if (m_positionsAmount == positionsAmount)
214 return;
215
216 m_positionsAmount = positionsAmount;
217 Q_EMIT positionsAmountChanged();
218 m_shapeDirty = true;
219}
220
221void QQuick3DParticleAttractor::updateShapePositions()
222{
223 m_shapePositionList.clear();
224 if (!system() || !m_shape)
225 return;
226
227 m_shape->m_system = system();
228
229 if (m_useCachedPositions) {
230 // Get count of particles positions needed
231 int pCount = 0;
232 if (m_positionsAmount > 0) {
233 pCount = m_positionsAmount;
234 } else {
235 if (!m_particles.isEmpty()) {
236 for (auto p : m_particles) {
237 auto pp = qobject_cast<QQuick3DParticle *>(object: p);
238 pCount += pp->maxAmount();
239 }
240 } else {
241 pCount = system()->particleCount();
242 }
243 }
244
245 m_shapePositionList.reserve(asize: pCount);
246 for (int i = 0; i < pCount; i++)
247 m_shapePositionList << m_shape->getPosition(particleIndex: i);
248 } else {
249 m_shapePositionList.clear();
250 m_shapePositionList.squeeze();
251 }
252
253 m_shapeDirty = false;
254}
255
256void QQuick3DParticleAttractor::prepareToAffect()
257{
258 if (m_shapeDirty)
259 updateShapePositions();
260 m_centerPos = position();
261 m_particleTransform = calculateParticleTransform(parent: parentNode(), systemSharedParent: m_systemSharedParent);
262}
263
264void QQuick3DParticleAttractor::affectParticle(const QQuick3DParticleData &sd, QQuick3DParticleDataCurrent *d, float time)
265{
266 if (!system())
267 return;
268
269 auto rand = system()->rand();
270 float duration = m_duration < 0 ? sd.lifetime : (m_duration / 1000.0f);
271 float durationVariation = m_durationVariation == 0
272 ? 0.0f
273 : (m_durationVariation / 1000.0f) - 2.0f * rand->get(particleIndex: sd.index, user: QPRand::AttractorDurationV) * (m_durationVariation / 1000.0f);
274 duration = std::max(a: duration + durationVariation, b: MIN_DURATION);
275 float pEnd = std::min(a: 1.0f, b: std::max(a: 0.0f, b: time / duration));
276 // TODO: Should we support easing?
277 //pEnd = easeInOutQuad(pEnd);
278
279 if (m_hideAtEnd && pEnd >= 1.0f) {
280 d->color.a = 0;
281 return;
282 }
283
284 float pStart = 1.0f - pEnd;
285 QVector3D pos = m_centerPos;
286
287 if (m_shape) {
288 if (m_useCachedPositions)
289 pos += m_shapePositionList[sd.index % m_shapePositionList.size()];
290 else
291 pos += m_shape->getPosition(particleIndex: sd.index);
292 }
293
294 if (!m_positionVariation.isNull()) {
295 pos.setX(pos.x() + m_positionVariation.x() - 2.0f * rand->get(particleIndex: sd.index, user: QPRand::AttractorPosVX) * m_positionVariation.x());
296 pos.setY(pos.y() + m_positionVariation.y() - 2.0f * rand->get(particleIndex: sd.index, user: QPRand::AttractorPosVY) * m_positionVariation.y());
297 pos.setZ(pos.z() + m_positionVariation.z() - 2.0f * rand->get(particleIndex: sd.index, user: QPRand::AttractorPosVZ) * m_positionVariation.z());
298 }
299
300 d->position = (pStart * d->position) + (pEnd * m_particleTransform.map(point: pos));
301}
302
303QT_END_NAMESPACE
304

source code of qtquick3d/src/quick3dparticles/qquick3dparticleattractor.cpp