Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qquick3dparticleattractor_p.h"
#include "qquick3dparticlerandomizer_p.h"
#include "qquick3dparticleutils_p.h"
QT_BEGIN_NAMESPACE
/*!
\qmltype Attractor3D
\inherits Affector3D
\inqmlmodule QtQuick3D.Particles3D
\brief Attracts particles towards a position or a shape.
\since 6.2
This element attracts particles towards a position inside the 3D view. To model
the gravity of a massive object whose center of gravity is far away, use \l Gravity3D.
The attraction position is defined either with the \l {Node::position}{position} and
\l positionVariation or with \l shape. If both are defined, \l shape is used.
*/
// Minimum duration in seconds
const float MIN_DURATION = 0.001f;
QQuick3DParticleAttractor::QQuick3DParticleAttractor(QQuick3DNode *parent)
: QQuick3DParticleAffector(parent)
, m_duration(-1)
, m_durationVariation(0)
{
}
/*!
\qmlproperty vector3d Attractor3D::positionVariation
This property defines the variation on attract position. It can be used to not attract
into a single point, but randomly towards a wider area. Here is an example how to attract
particles into some random point inside (50, 50, 50) cube at position (100, 0, 0) within
2 to 4 seconds:
\qml
Attractor3D {
position: Qt.vector3d(100, 0, 0)
positionVariation: Qt.vector3d(50, 50, 50)
duration: 3000
durationVariation: 1000
}
\endqml
The default value is \c (0, 0, 0) (no variation).
\sa {Node::position}, shape
*/
QVector3D QQuick3DParticleAttractor::positionVariation() const
{
return m_positionVariation;
}
void QQuick3DParticleAttractor::setPositionVariation(const QVector3D &positionVariation)
{
if (m_positionVariation == positionVariation)
return;
m_positionVariation = positionVariation;
Q_EMIT positionVariationChanged();
Q_EMIT update();
}
/*!
\qmlproperty ParticleAbstractShape3D Attractor3D::shape
This property defines a \l ParticleAbstractShape3D for particles attraction.
Each particle will be attracted into a random position inside this shape. This is an
alternative for defining \l {Node::position}{position} and \l positionVariation. Here
is an example how to attract particles into some random point inside sphere by the end
of the particles \l {ParticleEmitter3D::}{lifeSpan}:
\qml
Attractor3D {
position: Qt.vector3d(100, 0, 0)
shape: ParticleShape3D {
type: ParticleShape3D.Sphere
fill: true
}
}
\endqml
\sa {Node::position}, positionVariation
*/
QQuick3DParticleAbstractShape *QQuick3DParticleAttractor::shape() const
{
return m_shape;
}
void QQuick3DParticleAttractor::setShape(QQuick3DParticleAbstractShape *shape)
{
if (m_shape == shape)
return;
m_shape = shape;
m_shapeDirty = true;
Q_EMIT shapeChanged();
Q_EMIT update();
}
/*!
\qmlproperty int Attractor3D::duration
This property defines the duration in milliseconds how long it takes for particles to
reach the attaction position. When the value is -1, particle lifeSpan is used
as the duration.
The default value is \c -1.
*/
int QQuick3DParticleAttractor::duration() const
{
return m_duration;
}
void QQuick3DParticleAttractor::setDuration(int duration)
{
if (m_duration == duration)
return;
m_duration = duration;
Q_EMIT durationChanged();
Q_EMIT update();
}
/*!
\qmlproperty int Attractor3D::durationVariation
This property defines the duration variation in milliseconds. The actual duration to
reach attractor is between \c duration - \c durationVariation and \c duration + \c durationVariation.
The default value is \c 0 (no variation).
*/
int QQuick3DParticleAttractor::durationVariation() const
{
return m_durationVariation;
}
void QQuick3DParticleAttractor::setDurationVariation(int durationVariation)
{
if (m_durationVariation == durationVariation)
return;
m_durationVariation = durationVariation;
Q_EMIT durationVariationChanged();
Q_EMIT update();
}
/*!
\qmlproperty bool Attractor3D::hideAtEnd
This property defines if the particle should disappear when it reaches the attractor.
The default value is \c false.
*/
bool QQuick3DParticleAttractor::hideAtEnd() const
{
return m_hideAtEnd;
}
/*!
\qmlproperty bool Attractor3D::useCachedPositions
This property defines if the attractor caches possible positions within its shape.
Cached positions give less random results but are better for performance.
The default value is \c true.
*/
bool QQuick3DParticleAttractor::useCachedPositions() const
{
return m_useCachedPositions;
}
/*!
\qmlproperty int Attractor3D::positionsAmount
This property defines the amount of possible positions stored within the attractor shape.
By default the amount equals the particle count, but a lower amount can be used for a smaller cache.
Higher amount can be used for additional randomization.
*/
int QQuick3DParticleAttractor::positionsAmount() const
{
return m_positionsAmount;
}
void QQuick3DParticleAttractor::setHideAtEnd(bool hideAtEnd)
{
if (m_hideAtEnd == hideAtEnd)
return;
m_hideAtEnd = hideAtEnd;
Q_EMIT hideAtEndChanged();
Q_EMIT update();
}
void QQuick3DParticleAttractor::setUseCachedPositions(bool useCachedPositions)
{
if (m_useCachedPositions == useCachedPositions)
return;
m_useCachedPositions = useCachedPositions;
Q_EMIT useCachedPositionsChanged();
m_shapeDirty = true;
}
void QQuick3DParticleAttractor::setPositionsAmount(int positionsAmount)
{
if (m_positionsAmount == positionsAmount)
return;
m_positionsAmount = positionsAmount;
Q_EMIT positionsAmountChanged();
m_shapeDirty = true;
}
void QQuick3DParticleAttractor::updateShapePositions()
{
m_shapePositionList.clear();
if (!system() || !m_shape)
return;
m_shape->m_system = system();
if (m_useCachedPositions) {
// Get count of particles positions needed
int pCount = 0;
if (m_positionsAmount > 0) {
pCount = m_positionsAmount;
} else {
if (!m_particles.isEmpty()) {
for (auto *p : std::as_const(m_particles)) {
auto pp = qobject_cast<QQuick3DParticle *>(p);
pCount += pp->maxAmount();
}
} else {
pCount = system()->particleCount();
}
}
m_shapePositionList.reserve(pCount);
for (int i = 0; i < pCount; i++)
m_shapePositionList << m_shape->getPosition(i);
} else {
m_shapePositionList.clear();
m_shapePositionList.squeeze();
}
m_shapeDirty = false;
}
void QQuick3DParticleAttractor::prepareToAffect()
{
if (m_shapeDirty)
updateShapePositions();
m_centerPos = position();
m_particleTransform = calculateParticleTransform(parentNode(), m_systemSharedParent);
}
void QQuick3DParticleAttractor::affectParticle(const QQuick3DParticleData &sd, QQuick3DParticleDataCurrent *d, float time)
{
if (!system())
return;
auto rand = system()->rand();
float duration = m_duration < 0 ? sd.lifetime : (m_duration / 1000.0f);
float durationVariation = m_durationVariation == 0
? 0.0f
: (m_durationVariation / 1000.0f) - 2.0f * rand->get(sd.index, QPRand::AttractorDurationV) * (m_durationVariation / 1000.0f);
duration = std::max(duration + durationVariation, MIN_DURATION);
float pEnd = std::min(1.0f, std::max(0.0f, time / duration));
// TODO: Should we support easing?
//pEnd = easeInOutQuad(pEnd);
if (m_hideAtEnd && pEnd >= 1.0f) {
d->color.a = 0;
return;
}
float pStart = 1.0f - pEnd;
QVector3D pos = m_centerPos;
if (m_shape) {
if (m_useCachedPositions)
pos += m_shapePositionList[sd.index % m_shapePositionList.size()];
else
pos += m_shape->getPosition(sd.index);
}
if (!m_positionVariation.isNull()) {
pos.setX(pos.x() + m_positionVariation.x() - 2.0f * rand->get(sd.index, QPRand::AttractorPosVX) * m_positionVariation.x());
pos.setY(pos.y() + m_positionVariation.y() - 2.0f * rand->get(sd.index, QPRand::AttractorPosVY) * m_positionVariation.y());
pos.setZ(pos.z() + m_positionVariation.z() - 2.0f * rand->get(sd.index, QPRand::AttractorPosVZ) * m_positionVariation.z());
}
d->position = (pStart * d->position) + (pEnd * m_particleTransform.map(pos));
}
QT_END_NAMESPACE