fix: comprehensive boot warnings and exceptions — fixable silenced, unfixable diagnosed

Build system (5 gaps hardened):
- COOKBOOK_OFFLINE defaults to true (fork-mode)
- normalize_patch handles diff -ruN format
- New 'repo validate-patches' command (25/25 relibc patches)
- 14 patched Qt/Wayland/display recipes added to protected list
- relibc archive regenerated with current patch chain

Boot fixes (fixable):
- Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset)
- D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped)
- redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped)
- daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch)
- udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async)
- relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs
- greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait)
- greeter-ui: built and linked (header guard unification, sem_compat stubs removed)
- mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps
- greeter config: removed stale keymapd dependency from display/greeter services
- prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified

Unfixable (diagnosed, upstream):
- i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort
- kded6/greeter-ui: page fault 0x8 — Qt library null deref
- Thread panics fd != -1 — Rust std library on Redox
- DHCP timeout / eth0 MAC — QEMU user-mode networking
- hwrngd/thermald — no hardware RNG/thermal in VM
- live preload allocation — BIOS memory fragmentation, continues on demand
This commit is contained in:
2026-05-05 20:20:37 +01:00
parent a5f97b6632
commit f31522130f
81834 changed files with 11051982 additions and 108 deletions
@@ -0,0 +1,226 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QuickParticles Module:
#####################################################################
qt_internal_add_qml_module(QuickParticlesPrivate
URI "QtQuick.Particles"
VERSION "${PROJECT_VERSION}"
PLUGIN_TARGET particlesplugin
CLASS_NAME QtQuick2ParticlesPlugin
DEPENDENCIES
QtQuick/auto
INTERNAL_MODULE
SOURCES
qquickage.cpp qquickage_p.h
qquickangledirection.cpp qquickangledirection_p.h
qquickcumulativedirection.cpp qquickcumulativedirection_p.h
qquickcustomaffector.cpp qquickcustomaffector_p.h
qquickdirection.cpp qquickdirection_p.h
qquickellipseextruder.cpp qquickellipseextruder_p.h
qquickfriction.cpp qquickfriction_p.h
qquickgravity.cpp qquickgravity_p.h
qquickgroupgoal.cpp qquickgroupgoal_p.h
qquickimageparticle.cpp qquickimageparticle_p.h
qquickitemparticle.cpp qquickitemparticle_p.h
qquicklineextruder.cpp qquicklineextruder_p.h
qquickmaskextruder.cpp qquickmaskextruder_p.h
qquickparticleaffector.cpp qquickparticleaffector_p.h
qquickparticleemitter.cpp qquickparticleemitter_p.h
qquickparticleextruder.cpp qquickparticleextruder_p.h
qquickparticlegroup.cpp qquickparticlegroup_p.h
qquickparticlepainter.cpp qquickparticlepainter_p.h
qquickparticlesystem.cpp qquickparticlesystem_p.h
qquickpointattractor.cpp qquickpointattractor_p.h
qquickpointdirection.cpp qquickpointdirection_p.h
qquickrectangleextruder.cpp qquickrectangleextruder_p.h
qquickspritegoal.cpp qquickspritegoal_p.h
qquicktargetdirection.cpp qquicktargetdirection_p.h
qquicktrailemitter.cpp qquicktrailemitter_p.h
qquickturbulence.cpp qquickturbulence_p.h
qquickv4particledata.cpp qquickv4particledata_p.h
qquickwander.cpp qquickwander_p.h
qtquickparticlesglobal_p.h
NO_PCH_SOURCES
# these undef QT_NO_FOREACH
qquickcumulativedirection.cpp
qquickimageparticle.cpp
qquickitemparticle.cpp
qquickparticleaffector.cpp
qquickparticleemitter.cpp
qquickparticlegroup.cpp
qquickparticlepainter.cpp
qquicktrailemitter.cpp
qquickturbulence.cpp
# end undef QT_NO_FOREACH
DEFINES
QT_NO_INTEGER_EVENT_COORDINATES
QT_NO_URL_CAST_FROM_STRING
PUBLIC_LIBRARIES
Qt::CorePrivate
Qt::GuiPrivate
Qt::QmlPrivate
Qt::QuickPrivate
)
# Resources:
set(particles_resource_files
"particleresources/fuzzydot.png"
"particleresources/glowdot.png"
"particleresources/noise.png"
"particleresources/star.png"
)
qt_internal_add_resource(QuickParticlesPrivate "particles"
PREFIX
"/"
FILES
${particles_resource_files}
)
qt_internal_add_shaders(QuickParticlesPrivate "particles_shaders1"
SILENT
BATCHABLE
PRECOMPILE
OPTIMIZED
MULTIVIEW
GLSL
"150,120,100es,300es"
PREFIX
"/particles"
FILES
"shaders_ng/imageparticle.vert"
"shaders_ng/imageparticle.frag"
OUTPUTS
"shaders_ng/imageparticle_simplepoint.vert.qsb"
"shaders_ng/imageparticle_simplepoint.frag.qsb"
DEFINES
POINT
)
qt_internal_add_shaders(QuickParticlesPrivate "particles_shaders2"
SILENT
BATCHABLE
PRECOMPILE
OPTIMIZED
MULTIVIEW
GLSL
"150,120,100es,300es"
PREFIX
"/particles"
FILES
"shaders_ng/imageparticle.vert"
"shaders_ng/imageparticle.frag"
OUTPUTS
"shaders_ng/imageparticle_coloredpoint.vert.qsb"
"shaders_ng/imageparticle_coloredpoint.frag.qsb"
DEFINES
POINT
COLOR
)
qt_internal_add_shaders(QuickParticlesPrivate "particles_shaders3"
SILENT
BATCHABLE
PRECOMPILE
OPTIMIZED
MULTIVIEW
GLSL
"150,120,100es,300es"
PREFIX
"/particles"
FILES
"shaders_ng/imageparticle.vert"
"shaders_ng/imageparticle.frag"
OUTPUTS
"shaders_ng/imageparticle_colored.vert.qsb"
"shaders_ng/imageparticle_colored.frag.qsb"
DEFINES
COLOR
)
qt_internal_add_shaders(QuickParticlesPrivate "particles_shaders4"
SILENT
BATCHABLE
PRECOMPILE
OPTIMIZED
GLSL
"150,120,100es,300es"
PREFIX
"/particles"
FILES
"shaders_ng/imageparticle.vert"
"shaders_ng/imageparticle.frag"
OUTPUTS
"shaders_ng/imageparticle_deformed.vert.qsb"
"shaders_ng/imageparticle_deformed.frag.qsb"
DEFINES
DEFORM
COLOR
)
qt_internal_add_shaders(QuickParticlesPrivate "particles_shaders5"
SILENT
BATCHABLE
PRECOMPILE
OPTIMIZED
MULTIVIEW
GLSL
"150,120,100es,300es"
PREFIX
"/particles"
FILES
"shaders_ng/imageparticle.vert"
"shaders_ng/imageparticle.frag"
OUTPUTS
"shaders_ng/imageparticle_tabled.vert.qsb"
"shaders_ng/imageparticle_tabled.frag.qsb"
DEFINES
TABLE
DEFORM
COLOR
)
qt_internal_add_shaders(QuickParticlesPrivate "particles_shaders6"
SILENT
BATCHABLE
PRECOMPILE
OPTIMIZED
MULTIVIEW
GLSL
"150,120,100es,300es"
PREFIX
"/particles"
FILES
"shaders_ng/imageparticle.vert"
"shaders_ng/imageparticle.frag"
OUTPUTS
"shaders_ng/imageparticle_sprite.vert.qsb"
"shaders_ng/imageparticle_sprite.frag.qsb"
DEFINES
SPRITE
TABLE
DEFORM
COLOR
ZORDER_LOC
8
)
qt_internal_extend_target(QuickParticlesPrivate CONDITION MSVC
DEFINES
_CRT_SECURE_NO_WARNINGS
)
#### Keys ignored in scope 3:.:.:particles.pro:solaris-cc_x_:
# QMAKE_CXXFLAGS_RELEASE = "--O2"
qt_internal_extend_target(QuickParticlesPrivate CONDITION EXISTS "qqml_enable_gcov"
LIBRARIES
gcov
COMPILE_OPTIONS
-fno-elide-constructors
-fprofile-arcs
-ftest-coverage
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,81 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickage_p.h"
#include "qquickparticleemitter_p.h"
QT_BEGIN_NAMESPACE
/*!
\qmltype Age
\nativetype QQuickAgeAffector
\inqmlmodule QtQuick.Particles
\inherits ParticleAffector
\brief For altering particle ages.
\ingroup qtquick-particles
The Age affector allows you to alter where the particle is in its lifecycle. Common uses
are to expire particles prematurely, possibly giving them time to animate out.
The Age affector is also sometimes known as a 'Kill' affector, because with the default
parameters it will immediately expire all particles which it affects.
The Age affector only applies to particles which are still alive.
*/
/*!
\qmlproperty int QtQuick.Particles::Age::lifeLeft
The amount of life to set the particle to have. Affected particles
will advance to a point in their life where they will have this many
milliseconds left to live.
*/
/*!
\qmlproperty bool QtQuick.Particles::Age::advancePosition
advancePosition determines whether position, veclocity and acceleration are included in
the simulated aging done by the affector. If advancePosition is false,
then the position, velocity and acceleration will remain the same and only
other attributes (such as opacity) will advance in the simulation to where
it would normally be for that point in the particle's life. With advancePosition set to
true the position, velocity and acceleration will also advance to where it would
normally be by that point in the particle's life, making it advance its position
on screen.
Default value is true.
*/
QQuickAgeAffector::QQuickAgeAffector(QQuickItem *parent) :
QQuickParticleAffector(parent), m_lifeLeft(0), m_advancePosition(true)
{
}
bool QQuickAgeAffector::affectParticle(QQuickParticleData *d, qreal dt)
{
Q_UNUSED(dt);
if (d->stillAlive(m_system)){
float curT = m_system->timeInt / 1000.0f;
float ttl = m_lifeLeft / 1000.0f;
if (!m_advancePosition && ttl > 0){
float x = d->curX(m_system);
float vx = d->curVX(m_system);
float ax = d->curAX();
float y = d->curY(m_system);
float vy = d->curVY(m_system);
float ay = d->curAY();
d->t = curT - (d->lifeSpan - ttl);
d->setInstantaneousX(x, m_system);
d->setInstantaneousVX(vx, m_system);
d->setInstantaneousAX(ax, m_system);
d->setInstantaneousY(y, m_system);
d->setInstantaneousVY(vy, m_system);
d->setInstantaneousAY(ay, m_system);
} else {
d->t = curT - (d->lifeSpan - ttl);
}
return true;
}
return false;
}
QT_END_NAMESPACE
#include "moc_qquickage_p.cpp"
@@ -0,0 +1,72 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef KILLAFFECTOR_H
#define KILLAFFECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleaffector_p.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickAgeAffector : public QQuickParticleAffector
{
Q_OBJECT
Q_PROPERTY(int lifeLeft READ lifeLeft WRITE setLifeLeft NOTIFY lifeLeftChanged)
Q_PROPERTY(bool advancePosition READ advancePosition WRITE setAdvancePosition NOTIFY advancePositionChanged)
QML_NAMED_ELEMENT(Age)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickAgeAffector(QQuickItem *parent = nullptr);
int lifeLeft() const
{
return m_lifeLeft;
}
bool advancePosition() const
{
return m_advancePosition;
}
protected:
bool affectParticle(QQuickParticleData *d, qreal dt) override;
Q_SIGNALS:
void lifeLeftChanged(int arg);
void advancePositionChanged(bool arg);
public Q_SLOTS:
void setLifeLeft(int arg)
{
if (m_lifeLeft != arg) {
m_lifeLeft = arg;
Q_EMIT lifeLeftChanged(arg);
}
}
void setAdvancePosition(bool arg)
{
if (m_advancePosition != arg) {
m_advancePosition = arg;
Q_EMIT advancePositionChanged(arg);
}
}
private:
int m_lifeLeft;
bool m_advancePosition;
};
QT_END_NAMESPACE
#endif // KILLAFFECTOR_H
@@ -0,0 +1,84 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickangledirection_p.h"
#include <QRandomGenerator>
#include <qmath.h>
QT_BEGIN_NAMESPACE
const qreal CONV = 0.017453292519943295;
/*!
\qmltype AngleDirection
\nativetype QQuickAngleDirection
\inqmlmodule QtQuick.Particles
\ingroup qtquick-particles
\inherits Direction
\brief For specifying a direction that varies in angle.
The AngledDirection element allows both the specification of a direction by angle and magnitude,
as well as varying the parameters by angle or magnitude.
*/
/*!
\qmlproperty real QtQuick.Particles::AngleDirection::angle
This property specifies the base angle for the direction.
The angle of this direction will vary by no more than angleVariation
from this angle.
Angle is specified by degrees clockwise from straight right.
The default value is zero.
*/
/*!
\qmlproperty real QtQuick.Particles::AngleDirection::magnitude
This property specifies the base magnitude for the direction.
The magnitude of this direction will vary by no more than magnitudeVariation
from this magnitude.
Magnitude is specified in units of pixels per second.
The default value is zero.
*/
/*!
\qmlproperty real QtQuick.Particles::AngleDirection::angleVariation
This property specifies the maximum angle variation for the direction.
The angle of the direction will vary by up to angleVariation clockwise
and anticlockwise from the value specified in angle.
Angle is specified by degrees clockwise from straight right.
The default value is zero.
*/
/*!
\qmlproperty real QtQuick.Particles::AngleDirection::magnitudeVariation
This property specifies the base magnitude for the direction.
The magnitude of this direction will vary by no more than magnitudeVariation
from the base magnitude.
Magnitude is specified in units of pixels per second.
The default value is zero.
*/
QQuickAngleDirection::QQuickAngleDirection(QObject *parent) :
QQuickDirection(parent)
, m_angle(0)
, m_magnitude(0)
, m_angleVariation(0)
, m_magnitudeVariation(0)
{
}
QPointF QQuickAngleDirection::sample(const QPointF &from)
{
Q_UNUSED(from);
QPointF ret;
qreal theta = m_angle*CONV - m_angleVariation*CONV + QRandomGenerator::global()->generateDouble() * m_angleVariation*CONV * 2;
qreal mag = m_magnitude- m_magnitudeVariation + QRandomGenerator::global()->generateDouble() * m_magnitudeVariation * 2;
ret.setX(mag * qCos(theta));
ret.setY(mag * qSin(theta));
return ret;
}
QT_END_NAMESPACE
#include "moc_qquickangledirection_p.cpp"
@@ -0,0 +1,106 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QQuickANGLEDDIRECTION_H
#define QQuickANGLEDDIRECTION_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickdirection_p.h"
#include <QtQuickParticles/qtquickparticlesexports.h>
#include <QtQml/qqml.h>
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickAngleDirection : public QQuickDirection
{
Q_OBJECT
Q_PROPERTY(qreal angle READ angle WRITE setAngle NOTIFY angleChanged)
Q_PROPERTY(qreal magnitude READ magnitude WRITE setMagnitude NOTIFY magnitudeChanged)
Q_PROPERTY(qreal angleVariation READ angleVariation WRITE setAngleVariation NOTIFY angleVariationChanged)
Q_PROPERTY(qreal magnitudeVariation READ magnitudeVariation WRITE setMagnitudeVariation NOTIFY magnitudeVariationChanged)
QML_NAMED_ELEMENT(AngleDirection)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickAngleDirection(QObject *parent = nullptr);
QPointF sample(const QPointF &from) override;
qreal angle() const
{
return m_angle;
}
qreal magnitude() const
{
return m_magnitude;
}
qreal angleVariation() const
{
return m_angleVariation;
}
qreal magnitudeVariation() const
{
return m_magnitudeVariation;
}
Q_SIGNALS:
void angleChanged(qreal arg);
void magnitudeChanged(qreal arg);
void angleVariationChanged(qreal arg);
void magnitudeVariationChanged(qreal arg);
public Q_SLOTS:
void setAngle(qreal arg)
{
if (m_angle != arg) {
m_angle = arg;
Q_EMIT angleChanged(arg);
}
}
void setMagnitude(qreal arg)
{
if (m_magnitude != arg) {
m_magnitude = arg;
Q_EMIT magnitudeChanged(arg);
}
}
void setAngleVariation(qreal arg)
{
if (m_angleVariation != arg) {
m_angleVariation = arg;
Q_EMIT angleVariationChanged(arg);
}
}
void setMagnitudeVariation(qreal arg)
{
if (m_magnitudeVariation != arg) {
m_magnitudeVariation = arg;
Q_EMIT magnitudeVariationChanged(arg);
}
}
private:
qreal m_angle;
qreal m_magnitude;
qreal m_angleVariation;
qreal m_magnitudeVariation;
};
QT_END_NAMESPACE
#endif // QQuickANGLEDDIRECTION_H
@@ -0,0 +1,38 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
#include "qquickcumulativedirection_p.h"
QT_BEGIN_NAMESPACE
/*!
\qmltype CumulativeDirection
\nativetype QQuickCumulativeDirection
\inqmlmodule QtQuick.Particles
\inherits Direction
\brief For specifying a direction made of other directions.
\ingroup qtquick-particles
The CumulativeDirection element will act as a direction that sums the directions within it.
*/
QQuickCumulativeDirection::QQuickCumulativeDirection(QObject *parent):QQuickDirection(parent)
{
}
QQmlListProperty<QQuickDirection> QQuickCumulativeDirection::directions()
{
return QQmlListProperty<QQuickDirection>(this, &m_directions);//TODO: Proper list property
}
QPointF QQuickCumulativeDirection::sample(const QPointF &from)
{
QPointF ret;
foreach (QQuickDirection* dir, m_directions)
ret += dir->sample(from);
return ret;
}
QT_END_NAMESPACE
#include "moc_qquickcumulativedirection_p.cpp"
@@ -0,0 +1,41 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QQuickCUMULATIVEDIRECTION_P_H
#define QQuickCUMULATIVEDIRECTION_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickdirection_p.h"
#include <QQmlListProperty>
#include <QtQml/qqml.h>
#include <QtQuickParticles/qtquickparticlesexports.h>
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickCumulativeDirection : public QQuickDirection
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<QQuickDirection> directions READ directions)
Q_CLASSINFO("DefaultProperty", "directions")
QML_NAMED_ELEMENT(CumulativeDirection)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickCumulativeDirection(QObject *parent = nullptr);
QQmlListProperty<QQuickDirection> directions();
QPointF sample(const QPointF &from) override;
private:
QList<QQuickDirection*> m_directions;
};
QT_END_NAMESPACE
#endif // QQuickCUMULATIVEDIRECTION_P_H
@@ -0,0 +1,217 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickcustomaffector_p.h"
#include <private/qquickv4particledata_p.h>
#include <private/qqmlglobal_p.h>
#include <QtCore/qdebug.h>
QT_BEGIN_NAMESPACE
/*!
\qmltype Affector
\nativetype QQuickCustomAffector
\inqmlmodule QtQuick.Particles
\brief Applies alterations to the attributes of logical particles at any
point in their lifetime.
\inherits ParticleAffector
\ingroup qtquick-particles
Custom Affector manipulates the properties of the particles directly in
JavaScript.
*/
/*!
\qmlsignal QtQuick.Particles::Affector::affectParticles(Array particles, real dt)
This signal is emitted when particles are selected to be affected.
\a particles is an array of particle objects which can be directly
manipulated.
\a dt is the time since the last time it was affected. Use \a dt to
normalize trajectory manipulations to real time.
\note JavaScript is slower to execute, so it is not recommended to use
this in high-volume particle systems.
*/
/*!
\qmlproperty StochasticDirection QtQuick.Particles::Affector::position
Affected particles will have their position set to this direction,
relative to the ParticleSystem. When interpreting directions as points,
imagine it as an arrow with the base at the 0,0 of the ParticleSystem and the
tip at where the specified position will be.
*/
/*!
\qmlproperty StochasticDirection QtQuick.Particles::Affector::velocity
Affected particles will have their velocity set to this direction.
*/
/*!
\qmlproperty StochasticDirection QtQuick.Particles::Affector::acceleration
Affected particles will have their acceleration set to this direction.
*/
/*!
\qmlproperty bool QtQuick.Particles::Affector::relative
Whether the affected particles have their existing position/velocity/acceleration added
to the new one.
Default is true.
*/
QQuickCustomAffector::QQuickCustomAffector(QQuickItem *parent) :
QQuickParticleAffector(parent)
, m_position(&m_nullVector)
, m_velocity(&m_nullVector)
, m_acceleration(&m_nullVector)
, m_relative(true)
{
}
bool QQuickCustomAffector::isAffectConnected()
{
IS_SIGNAL_CONNECTED(
this, QQuickCustomAffector, affectParticles,
(const QList<QQuickV4ParticleData> &, qreal));
}
void QQuickCustomAffector::affectSystem(qreal dt)
{
//Acts a bit differently, just emits affected for everyone it might affect, when the only thing is connecting to affected(x,y)
bool justAffected = (m_acceleration == &m_nullVector
&& m_velocity == &m_nullVector
&& m_position == &m_nullVector
&& isAffectedConnected());
if (!isAffectConnected() && !justAffected) {
QQuickParticleAffector::affectSystem(dt);
return;
}
if (!m_enabled)
return;
updateOffsets();
QList<QQuickParticleData*> toAffect;
for (const QQuickParticleGroupData *gd : std::as_const(m_system->groupData)) {
if (activeGroup(gd->index)) {
for (QQuickParticleData *d : gd->data) {
if (shouldAffect(d)) {
toAffect << d;
}
}
}
}
if (toAffect.isEmpty())
return;
if (justAffected) {
for (const QQuickParticleData *d : std::as_const(toAffect)) {//Not postAffect to avoid saying the particle changed
if (m_onceOff)
m_onceOffed << std::make_pair(d->groupId, d->index);
emit affected(d->curX(m_system), d->curY(m_system));
}
return;
}
if (m_onceOff)
dt = 1.0;
QList<QQuickV4ParticleData> particles;
particles.reserve(toAffect.size());
for (QQuickParticleData *data: std::as_const(toAffect))
particles.push_back(data->v4Value(m_system));
const auto doAffect = [&](qreal dt) {
affectProperties(toAffect, dt);
emit affectParticles(particles, dt);
};
if (dt >= simulationCutoff || dt <= simulationDelta) {
doAffect(dt);
} else {
int realTime = m_system->timeInt;
m_system->timeInt -= dt * 1000.0;
while (dt > simulationDelta) {
m_system->timeInt += simulationDelta * 1000.0;
dt -= simulationDelta;
doAffect(simulationDelta);
}
m_system->timeInt = realTime;
if (dt > 0.0)
doAffect(dt);
}
for (QQuickParticleData *d : std::as_const(toAffect))
if (d->update == 1.0)
postAffect(d);
}
bool QQuickCustomAffector::affectParticle(QQuickParticleData *d, qreal dt)
{
//This does the property based affecting, called by superclass if signal isn't hooked up.
bool changed = false;
QPointF curPos(d->curX(m_system), d->curY(m_system));
if (m_acceleration != &m_nullVector){
QPointF pos = m_acceleration->sample(curPos);
QPointF curAcc = QPointF(d->curAX(), d->curAY());
if (m_relative) {
pos *= dt;
pos += curAcc;
}
if (pos != curAcc) {
d->setInstantaneousAX(pos.x(), m_system);
d->setInstantaneousAY(pos.y(), m_system);
changed = true;
}
}
if (m_velocity != &m_nullVector){
QPointF pos = m_velocity->sample(curPos);
QPointF curVel = QPointF(d->curVX(m_system), d->curVY(m_system));
if (m_relative) {
pos *= dt;
pos += curVel;
}
if (pos != curVel) {
d->setInstantaneousVX(pos.x(), m_system);
d->setInstantaneousVY(pos.y(), m_system);
changed = true;
}
}
if (m_position != &m_nullVector){
QPointF pos = m_position->sample(curPos);
if (m_relative) {
pos *= dt;
pos += curPos;
}
if (pos != curPos) {
d->setInstantaneousX(pos.x(), m_system);
d->setInstantaneousY(pos.y(), m_system);
changed = true;
}
}
return changed;
}
void QQuickCustomAffector::affectProperties(const QList<QQuickParticleData*> &particles, qreal dt)
{
for (QQuickParticleData *d : particles)
if ( affectParticle(d, dt) )
d->update = 1.0;
}
QT_END_NAMESPACE
#include "moc_qquickcustomaffector_p.cpp"
@@ -0,0 +1,136 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef CUSTOMAFFECTOR_H
#define CUSTOMAFFECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QObject>
#include <QtQml/qqml.h>
#include "qquickparticlesystem_p.h"
#include "qquickparticleextruder_p.h"
#include "qquickparticleaffector_p.h"
#include "qquickdirection_p.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickCustomAffector : public QQuickParticleAffector
{
Q_OBJECT
Q_PROPERTY(bool relative READ relative WRITE setRelative NOTIFY relativeChanged)
Q_PROPERTY(QQuickDirection *position READ position WRITE setPosition NOTIFY positionChanged RESET positionReset)
Q_PROPERTY(QQuickDirection *velocity READ velocity WRITE setVelocity NOTIFY velocityChanged RESET velocityReset)
Q_PROPERTY(QQuickDirection *acceleration READ acceleration WRITE setAcceleration NOTIFY accelerationChanged RESET accelerationReset)
QML_NAMED_ELEMENT(Affector)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickCustomAffector(QQuickItem *parent = nullptr);
void affectSystem(qreal dt) override;
QQuickDirection * position() const
{
return m_position;
}
QQuickDirection * velocity() const
{
return m_velocity;
}
QQuickDirection * acceleration() const
{
return m_acceleration;
}
void positionReset()
{
m_position = &m_nullVector;
}
void velocityReset()
{
m_velocity = &m_nullVector;
}
void accelerationReset()
{
m_acceleration = &m_nullVector;
}
bool relative() const
{
return m_relative;
}
Q_SIGNALS:
void affectParticles(const QList<QQuickV4ParticleData> &particles, qreal dt);
void positionChanged(QQuickDirection * arg);
void velocityChanged(QQuickDirection * arg);
void accelerationChanged(QQuickDirection * arg);
void relativeChanged(bool arg);
public Q_SLOTS:
void setPosition(QQuickDirection * arg)
{
if (m_position != arg) {
m_position = arg;
Q_EMIT positionChanged(arg);
}
}
void setVelocity(QQuickDirection * arg)
{
if (m_velocity != arg) {
m_velocity = arg;
Q_EMIT velocityChanged(arg);
}
}
void setAcceleration(QQuickDirection * arg)
{
if (m_acceleration != arg) {
m_acceleration = arg;
Q_EMIT accelerationChanged(arg);
}
}
void setRelative(bool arg)
{
if (m_relative != arg) {
m_relative = arg;
Q_EMIT relativeChanged(arg);
}
}
protected:
bool isAffectConnected();
bool affectParticle(QQuickParticleData *d, qreal dt) override;
private:
void affectProperties(const QList<QQuickParticleData*> &particles, qreal dt);
QQuickDirection * m_position;
QQuickDirection * m_velocity;
QQuickDirection * m_acceleration;
QQuickDirection m_nullVector;
bool m_relative;
};
QT_END_NAMESPACE
#endif // CUSTOMAFFECTOR_H
@@ -0,0 +1,31 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickdirection_p.h"
QT_BEGIN_NAMESPACE
/*!
\qmltype Direction
\nativetype QQuickDirection
\inqmlmodule QtQuick.Particles
\brief For specifying a vector space.
\ingroup qtquick-particles
*/
QQuickDirection::QQuickDirection(QObject *parent) :
QObject(parent)
{
}
QPointF QQuickDirection::sample(const QPointF &from)
{
Q_UNUSED(from);
return QPointF();
}
QT_END_NAMESPACE
#include "moc_qquickdirection_p.cpp"
@@ -0,0 +1,45 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef VARYINGVECTOR_H
#define VARYINGVECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QObject>
#include <QPointF>
#include <QtQml/qqml.h>
#include <QtCore/private/qglobal_p.h>
#include <QtQuickParticles/qtquickparticlesexports.h>
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickDirection : public QObject
{
Q_OBJECT
QML_NAMED_ELEMENT(NullVector)
QML_ADDED_IN_VERSION(2, 0)
QML_UNCREATABLE("Abstract type. Use one of the inheriting types instead.")
public:
explicit QQuickDirection(QObject *parent = nullptr);
virtual QPointF sample(const QPointF &from);
Q_SIGNALS:
public Q_SLOTS:
protected:
};
QT_END_NAMESPACE
#endif // VARYINGVECTOR_H
@@ -0,0 +1,54 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickellipseextruder_p.h"
#include <qmath.h>
#include <qrandom.h>
QT_BEGIN_NAMESPACE
/*!
\qmltype EllipseShape
\nativetype QQuickEllipseExtruder
\inqmlmodule QtQuick.Particles
\ingroup qtquick-particles
\inherits Shape
\brief Represents an ellipse to other particle system elements.
This shape can be used by Emitter subclasses and Affector subclasses to have
them act upon an ellipse shaped area.
*/
QQuickEllipseExtruder::QQuickEllipseExtruder(QObject *parent) :
QQuickParticleExtruder(parent)
, m_fill(true)
{
}
/*!
\qmlproperty bool QtQuick.Particles::EllipseShape::fill
If fill is true the ellipse is filled; otherwise it is just a border.
Default is true.
*/
QPointF QQuickEllipseExtruder::extrude(const QRectF & r)
{
qreal theta = QRandomGenerator::global()->bounded(2 * M_PI);
qreal mag = m_fill ? QRandomGenerator::global()->generateDouble() : 1;
return QPointF(r.x() + r.width()/2 + mag * (r.width()/2) * qCos(theta),
r.y() + r.height()/2 + mag * (r.height()/2) * qSin(theta));
}
bool QQuickEllipseExtruder::contains(const QRectF &bounds, const QPointF &point)
{
if (!bounds.contains(point))
return false;
QPointF relPoint(bounds.center() - point);
qreal xa = relPoint.x()/bounds.width();
qreal yb = relPoint.y()/bounds.height();
return (xa * xa + yb * yb) < 0.25;
}
QT_END_NAMESPACE
#include "moc_qquickellipseextruder_p.cpp"
@@ -0,0 +1,55 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef ELLIPSEEXTRUDER_H
#define ELLIPSEEXTRUDER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleextruder_p.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickEllipseExtruder : public QQuickParticleExtruder
{
Q_OBJECT
Q_PROPERTY(bool fill READ fill WRITE setFill NOTIFY fillChanged)//###Use base class? If it's still box
QML_NAMED_ELEMENT(EllipseShape)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickEllipseExtruder(QObject *parent = nullptr);
QPointF extrude(const QRectF &) override;
bool contains(const QRectF &bounds, const QPointF &point) override;
bool fill() const
{
return m_fill;
}
Q_SIGNALS:
void fillChanged(bool arg);
public Q_SLOTS:
void setFill(bool arg)
{
if (m_fill != arg) {
m_fill = arg;
Q_EMIT fillChanged(arg);
}
}
private:
bool m_fill;
};
QT_END_NAMESPACE
#endif // ELLIPSEEXTRUDER_H
@@ -0,0 +1,79 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickfriction_p.h"
#include <qmath.h>
QT_BEGIN_NAMESPACE
/*!
\qmltype Friction
\nativetype QQuickFrictionAffector
\inqmlmodule QtQuick.Particles
\ingroup qtquick-particles
\inherits ParticleAffector
\brief For applying friction proportional to the particle's current velocity.
*/
/*!
\qmlproperty real QtQuick.Particles::Friction::factor
A drag will be applied to moving objects which is this factor of their current velocity.
*/
/*!
\qmlproperty real QtQuick.Particles::Friction::threshold
The drag will only be applied to objects with a velocity above the threshold velocity. The
drag applied will bring objects down to the threshold velocity, but no further.
The default threshold is 0
*/
static qreal sign(qreal a)
{
return a >= 0 ? 1 : -1;
}
static const qreal epsilon = 0.00001;
QQuickFrictionAffector::QQuickFrictionAffector(QQuickItem *parent) :
QQuickParticleAffector(parent), m_factor(0.0), m_threshold(0.0)
{
}
bool QQuickFrictionAffector::affectParticle(QQuickParticleData *d, qreal dt)
{
if (!m_factor)
return false;
qreal curVX = d->curVX(m_system);
qreal curVY = d->curVY(m_system);
if (!curVX && !curVY)
return false;
qreal newVX = curVX + (curVX * m_factor * -1 * dt);
qreal newVY = curVY + (curVY * m_factor * -1 * dt);
if (!m_threshold) {
if (sign(curVX) != sign(newVX))
newVX = 0;
if (sign(curVY) != sign(newVY))
newVY = 0;
} else {
qreal curMag = qSqrt(curVX*curVX + curVY*curVY);
if (curMag <= m_threshold + epsilon)
return false;
qreal newMag = qSqrt(newVX*newVX + newVY*newVY);
if (newMag <= m_threshold + epsilon || //went past the threshold, stop there instead
sign(curVX) != sign(newVX) || //went so far past maybe it came out the other side!
sign(curVY) != sign(newVY)) {
qreal theta = qAtan2(curVY, curVX);
newVX = m_threshold * qCos(theta);
newVY = m_threshold * qSin(theta);
}
}
d->setInstantaneousVX(newVX, m_system);
d->setInstantaneousVY(newVY, m_system);
return true;
}
QT_END_NAMESPACE
#include "moc_qquickfriction_p.cpp"
@@ -0,0 +1,73 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef FRICTIONAFFECTOR_H
#define FRICTIONAFFECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleaffector_p.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickFrictionAffector : public QQuickParticleAffector
{
Q_OBJECT
Q_PROPERTY(qreal factor READ factor WRITE setFactor NOTIFY factorChanged)
Q_PROPERTY(qreal threshold READ threshold WRITE setThreshold NOTIFY thresholdChanged)
QML_NAMED_ELEMENT(Friction)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickFrictionAffector(QQuickItem *parent = nullptr);
qreal factor() const
{
return m_factor;
}
qreal threshold() const
{
return m_threshold;
}
protected:
bool affectParticle(QQuickParticleData *d, qreal dt) override;
Q_SIGNALS:
void factorChanged(qreal arg);
void thresholdChanged(qreal arg);
public Q_SLOTS:
void setFactor(qreal arg)
{
if (m_factor != arg) {
m_factor = arg;
Q_EMIT factorChanged(arg);
}
}
void setThreshold(qreal arg)
{
if (m_threshold != arg) {
m_threshold = arg;
Q_EMIT thresholdChanged(arg);
}
}
private:
qreal m_factor;
qreal m_threshold;
};
QT_END_NAMESPACE
#endif // FRICTIONAFFECTOR_H
@@ -0,0 +1,102 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <QtQml/qqmlinfo.h>
#include <QtCore/QtMath>
#include "qquickgravity_p.h"
QT_BEGIN_NAMESPACE
/*!
\qmltype Gravity
\nativetype QQuickGravityAffector
\inqmlmodule QtQuick.Particles
\ingroup qtquick-particles
\inherits ParticleAffector
\brief For applying acceleration in an angle.
This element will accelerate all affected particles to a vector of
the specified magnitude in the specified angle. If the angle and acceleration do
not vary, it is more efficient to set the specified acceleration on the Emitter.
This element models the gravity of a massive object whose center of
gravity is far away (and thus the gravitational pull is effectively constant
across the scene). To model the gravity of an object near or inside the scene,
use PointAttractor.
*/
/*!
\qmlproperty real QtQuick.Particles::Gravity::magnitude
Pixels per second that objects will be accelerated by.
*/
void QQuickGravityAffector::setMagnitude(qreal arg)
{
if (m_magnitude != arg) {
m_magnitude = arg;
m_needRecalc = true;
emit magnitudeChanged(arg);
}
}
qreal QQuickGravityAffector::magnitude() const
{
return m_magnitude;
}
/*!
\qmlproperty real QtQuick.Particles::Gravity::acceleration
\deprecated
\warning The name for this property has changed to magnitude, use it instead.
*/
void QQuickGravityAffector::setAcceleration(qreal arg)
{
qmlWarning(this) << "The acceleration property is deprecated. Please use magnitude instead.";
setMagnitude(arg);
}
/*!
\qmlproperty real QtQuick.Particles::Gravity::angle
Angle of acceleration.
*/
void QQuickGravityAffector::setAngle(qreal arg)
{
if (m_angle != arg) {
m_angle = arg;
m_needRecalc = true;
emit angleChanged(arg);
}
}
qreal QQuickGravityAffector::angle() const
{
return m_angle;
}
QQuickGravityAffector::QQuickGravityAffector(QQuickItem *parent) :
QQuickParticleAffector(parent), m_magnitude(-10), m_angle(90), m_needRecalc(true)
{
}
bool QQuickGravityAffector::affectParticle(QQuickParticleData *d, qreal dt)
{
if (!m_magnitude)
return false;
if (m_needRecalc) {
m_needRecalc = false;
m_dx = m_magnitude * qCos(qDegreesToRadians(m_angle));
m_dy = m_magnitude * qSin(qDegreesToRadians(m_angle));
}
d->setInstantaneousVX(d->curVX(m_system) + m_dx*dt, m_system);
d->setInstantaneousVY(d->curVY(m_system) + m_dy*dt, m_system);
return true;
}
QT_END_NAMESPACE
#include "moc_qquickgravity_p.cpp"
@@ -0,0 +1,57 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef GRAVITYAFFECTOR_H
#define GRAVITYAFFECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleaffector_p.h"
#include <QtQml/qqml.h>
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickGravityAffector : public QQuickParticleAffector
{
Q_OBJECT
Q_PROPERTY(qreal magnitude READ magnitude WRITE setMagnitude NOTIFY magnitudeChanged)
Q_PROPERTY(qreal acceleration READ magnitude WRITE setAcceleration NOTIFY magnitudeChanged)
Q_PROPERTY(qreal angle READ angle WRITE setAngle NOTIFY angleChanged)
QML_NAMED_ELEMENT(Gravity)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickGravityAffector(QQuickItem *parent = nullptr);
qreal magnitude() const;
qreal angle() const;
protected:
bool affectParticle(QQuickParticleData *d, qreal dt) override;
Q_SIGNALS:
void magnitudeChanged(qreal arg);
void angleChanged(qreal arg);
public Q_SLOTS:
void setMagnitude(qreal arg);
void setAcceleration(qreal arg);
void setAngle(qreal arg);
private:
qreal m_magnitude;
qreal m_angle;
bool m_needRecalc;
qreal m_dx;
qreal m_dy;
};
QT_END_NAMESPACE
#endif // GRAVITYAFFECTOR_H
@@ -0,0 +1,74 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickgroupgoal_p.h"
#include <private/qquickspriteengine_p.h>
#include <private/qquicksprite_p.h>
#include "qquickimageparticle_p.h"
#include <QDebug>
QT_BEGIN_NAMESPACE
/*!
\qmltype GroupGoal
\nativetype QQuickGroupGoalAffector
\inqmlmodule QtQuick.Particles
\ingroup qtquick-particles
\inherits ParticleAffector
\brief For changing the state of a group of a particle.
*/
/*!
\qmlproperty string QtQuick.Particles::GroupGoal::goalState
The name of the group which the affected particles should move to.
Groups can have defined durations and transitions between them, setting goalState
will cause it to disregard any path weightings (including 0) and head down the path
which will reach the goalState quickest. It will pass through intermediate groups
on that path for their respective durations.
*/
/*!
\qmlproperty bool QtQuick.Particles::GroupGoal::jump
If true, affected particles will jump directly to the target group instead of taking the
shortest valid path to get there. They will also not finish their current state,
but immediately move to the beginning of the goal state.
Default is false.
*/
QQuickGroupGoalAffector::QQuickGroupGoalAffector(QQuickItem *parent) :
QQuickParticleAffector(parent), m_jump(false)
{
m_ignoresTime = true;
}
void QQuickGroupGoalAffector::setGoalState(const QString &arg)
{
if (m_goalState != arg) {
m_goalState = arg;
emit goalStateChanged(arg);
}
}
bool QQuickGroupGoalAffector::affectParticle(QQuickParticleData *d, qreal dt)
{
Q_UNUSED(dt);
QQuickStochasticEngine *engine = m_system->stateEngine;
int index = d->systemIndex;
int goalIdx = m_system->groupIds[m_goalState];
if (!engine){//no stochastic states defined. So cut out the engine
//TODO: It's possible to move to a group that is intermediate and not used by painters or emitters - but right now that will redirect to the default group
m_system->moveGroups(d, goalIdx);
return true;
}else if (engine->curState(index) != goalIdx){
engine->setGoal(goalIdx, index, m_jump);
return true;
}
return false;
}
QT_END_NAMESPACE
#include "moc_qquickgroupgoal_p.cpp"
@@ -0,0 +1,71 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef GROUPGOALAFFECTOR_H
#define GROUPGOALAFFECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleaffector_p.h"
QT_BEGIN_NAMESPACE
class QQuickStochasticEngine;
class Q_QUICKPARTICLES_EXPORT QQuickGroupGoalAffector : public QQuickParticleAffector
{
Q_OBJECT
Q_PROPERTY(QString goalState READ goalState WRITE setGoalState NOTIFY goalStateChanged)
Q_PROPERTY(bool jump READ jump WRITE setJump NOTIFY jumpChanged)
QML_NAMED_ELEMENT(GroupGoal)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickGroupGoalAffector(QQuickItem *parent = nullptr);
QString goalState() const
{
return m_goalState;
}
bool jump() const
{
return m_jump;
}
protected:
bool affectParticle(QQuickParticleData *d, qreal dt) override;
Q_SIGNALS:
void goalStateChanged(const QString &arg);
void jumpChanged(bool arg);
public Q_SLOTS:
void setGoalState(const QString &arg);
void setJump(bool arg)
{
if (m_jump != arg) {
m_jump = arg;
Q_EMIT jumpChanged(arg);
}
}
private:
QString m_goalState;
bool m_jump;
};
QT_END_NAMESPACE
#endif // GROUPGOALAFFECTOR_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,457 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QQUICKIMAGEPARTICLE_P_H
#define QQUICKIMAGEPARTICLE_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticlepainter_p.h"
#include "qquickdirection_p.h"
#include <private/qquickpixmap_p.h>
#include <QQmlListProperty>
#include <QtGui/qcolor.h>
#include <QtQuick/qsgmaterial.h>
QT_BEGIN_NAMESPACE
class ImageMaterialData;
class QSGGeometryNode;
class QSGMaterial;
class QQuickSprite;
class QQuickStochasticEngine;
class QRhi;
struct SimplePointVertex {
float x;
float y;
float t;
float lifeSpan;
float size;
float endSize;
float vx;
float vy;
float ax;
float ay;
};
struct ColoredPointVertex {
float x;
float y;
float t;
float lifeSpan;
float size;
float endSize;
float vx;
float vy;
float ax;
float ay;
Color4ub color;
};
// Like Colored, but using DrawTriangles instead of DrawPoints
struct ColoredVertex {
float x;
float y;
float t;
float lifeSpan;
float size;
float endSize;
float vx;
float vy;
float ax;
float ay;
Color4ub color;
uchar tx;
uchar ty;
uchar _padding[2]; // feel free to use
};
struct DeformableVertex {
float x;
float y;
float rotation;
float rotationVelocity;
float t;
float lifeSpan;
float size;
float endSize;
float vx;
float vy;
float ax;
float ay;
Color4ub color;
float xx;
float xy;
float yx;
float yy;
uchar tx;
uchar ty;
uchar autoRotate;
uchar _padding; // feel free to use
};
struct SpriteVertex {
float x;
float y;
float rotation;
float rotationVelocity;
float t;
float lifeSpan;
float size;
float endSize;
float vx;
float vy;
float ax;
float ay;
Color4ub color;
float xx;
float xy;
float yx;
float yy;
uchar tx;
uchar ty;
uchar autoRotate;
uchar _padding; // feel free to use
float animW;
float animH;
float animProgress;
float animX1;
float animY1;
float animX2;
};
template <typename Vertex>
struct Vertices {
Vertex v1;
Vertex v2;
Vertex v3;
Vertex v4;
};
class ImageMaterial : public QSGMaterial
{
public:
virtual ImageMaterialData *state() = 0;
};
class Q_QUICKPARTICLES_EXPORT QQuickImageParticle : public QQuickParticlePainter
{
Q_OBJECT
Q_PROPERTY(QUrl source READ image WRITE setImage NOTIFY imageChanged)
Q_PROPERTY(QQmlListProperty<QQuickSprite> sprites READ sprites)
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
//### Is it worth having progress like Image has?
//Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged)
Q_PROPERTY(QUrl colorTable READ colortable WRITE setColortable NOTIFY colortableChanged)
Q_PROPERTY(QUrl sizeTable READ sizetable WRITE setSizetable NOTIFY sizetableChanged)
Q_PROPERTY(QUrl opacityTable READ opacitytable WRITE setOpacitytable NOTIFY opacitytableChanged)
//###Now just colorize - add a flag for 'solid' color particles(where the img is just a mask?)?
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged RESET resetColor)
//Stacks (added) with individual colorVariations
Q_PROPERTY(qreal colorVariation READ colorVariation WRITE setColorVariation NOTIFY colorVariationChanged RESET resetColor)
Q_PROPERTY(qreal redVariation READ redVariation WRITE setRedVariation NOTIFY redVariationChanged RESET resetColor)
Q_PROPERTY(qreal greenVariation READ greenVariation WRITE setGreenVariation NOTIFY greenVariationChanged RESET resetColor)
Q_PROPERTY(qreal blueVariation READ blueVariation WRITE setBlueVariation NOTIFY blueVariationChanged RESET resetColor)
//Stacks (multiplies) with the Alpha in the color, mostly here so you can use svg color names (which have full alpha)
Q_PROPERTY(qreal alpha READ alpha WRITE setAlpha NOTIFY alphaChanged RESET resetColor)
Q_PROPERTY(qreal alphaVariation READ alphaVariation WRITE setAlphaVariation NOTIFY alphaVariationChanged RESET resetColor)
Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged RESET resetRotation)
Q_PROPERTY(qreal rotationVariation READ rotationVariation WRITE setRotationVariation NOTIFY rotationVariationChanged RESET resetRotation)
Q_PROPERTY(qreal rotationVelocity READ rotationVelocity WRITE setRotationVelocity NOTIFY rotationVelocityChanged RESET resetRotation)
Q_PROPERTY(qreal rotationVelocityVariation READ rotationVelocityVariation WRITE setRotationVelocityVariation NOTIFY rotationVelocityVariationChanged RESET resetRotation)
//If true, then will face the direction of motion. Stacks with rotation, e.g. setting rotation
//to 180 will lead to facing away from the direction of motion
Q_PROPERTY(bool autoRotation READ autoRotation WRITE setAutoRotation NOTIFY autoRotationChanged RESET resetRotation)
//xVector is the vector from the top-left point to the top-right point, and is multiplied by current size
Q_PROPERTY(QQuickDirection* xVector READ xVector WRITE setXVector NOTIFY xVectorChanged RESET resetDeformation)
//yVector is the same, but top-left to bottom-left. The particle is always a parallelogram.
Q_PROPERTY(QQuickDirection* yVector READ yVector WRITE setYVector NOTIFY yVectorChanged RESET resetDeformation)
Q_PROPERTY(bool spritesInterpolate READ spritesInterpolate WRITE setSpritesInterpolate NOTIFY spritesInterpolateChanged)
Q_PROPERTY(EntryEffect entryEffect READ entryEffect WRITE setEntryEffect NOTIFY entryEffectChanged)
QML_NAMED_ELEMENT(ImageParticle)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickImageParticle(QQuickItem *parent = nullptr);
virtual ~QQuickImageParticle();
enum Status { Null, Ready, Loading, Error };
Q_ENUM(Status)
QQmlListProperty<QQuickSprite> sprites();
QQuickStochasticEngine* spriteEngine() {return m_spriteEngine;}
enum EntryEffect {
None = 0,
Fade = 1,
Scale = 2
};
Q_ENUM(EntryEffect)
enum PerformanceLevel{//TODO: Expose?
Unknown = 0,
SimplePoint,
ColoredPoint,
Colored,
Deformable,
Tabled,
Sprites
};
QUrl image() const { return m_image ? m_image->source : QUrl(); }
void setImage(const QUrl &image);
QUrl colortable() const { return m_colorTable ? m_colorTable->source : QUrl(); }
void setColortable(const QUrl &table);
QUrl sizetable() const { return m_sizeTable ? m_sizeTable->source : QUrl(); }
void setSizetable (const QUrl &table);
QUrl opacitytable() const { return m_opacityTable ? m_opacityTable->source : QUrl(); }
void setOpacitytable(const QUrl &table);
QColor color() const { return m_color; }
void setColor(const QColor &color);
qreal colorVariation() const { return m_color_variation; }
void setColorVariation(qreal var);
qreal alphaVariation() const { return m_alphaVariation; }
qreal alpha() const { return m_alpha; }
qreal redVariation() const { return m_redVariation; }
qreal greenVariation() const { return m_greenVariation; }
qreal blueVariation() const { return m_blueVariation; }
qreal rotation() const { return m_rotation; }
qreal rotationVariation() const { return m_rotationVariation; }
qreal rotationVelocity() const { return m_rotationVelocity; }
qreal rotationVelocityVariation() const { return m_rotationVelocityVariation; }
bool autoRotation() const { return m_autoRotation; }
QQuickDirection* xVector() const { return m_xVector; }
QQuickDirection* yVector() const { return m_yVector; }
bool spritesInterpolate() const { return m_spritesInterpolate; }
bool bypassOptimizations() const { return m_bypassOptimizations; }
EntryEffect entryEffect() const { return m_entryEffect; }
Status status() const { return m_status; }
void resetColor();
void resetRotation();
void resetDeformation();
Q_SIGNALS:
void imageChanged();
void colortableChanged();
void sizetableChanged();
void opacitytableChanged();
void colorChanged();
void colorVariationChanged();
void alphaVariationChanged(qreal arg);
void alphaChanged(qreal arg);
void redVariationChanged(qreal arg);
void greenVariationChanged(qreal arg);
void blueVariationChanged(qreal arg);
void rotationChanged(qreal arg);
void rotationVariationChanged(qreal arg);
void rotationVelocityChanged(qreal arg);
void rotationVelocityVariationChanged(qreal arg);
void autoRotationChanged(bool arg);
void xVectorChanged(QQuickDirection* arg);
void yVectorChanged(QQuickDirection* arg);
void spritesInterpolateChanged(bool arg);
void bypassOptimizationsChanged(bool arg);
void entryEffectChanged(EntryEffect arg);
void statusChanged(Status arg);
public Q_SLOTS:
void setAlphaVariation(qreal arg);
void setAlpha(qreal arg);
void setRedVariation(qreal arg);
void setGreenVariation(qreal arg);
void setBlueVariation(qreal arg);
void setRotation(qreal arg);
void setRotationVariation(qreal arg);
void setRotationVelocity(qreal arg);
void setRotationVelocityVariation(qreal arg);
void setAutoRotation(bool arg);
void setXVector(QQuickDirection* arg);
void setYVector(QQuickDirection* arg);
void setSpritesInterpolate(bool arg);
void setBypassOptimizations(bool arg);
void setEntryEffect(EntryEffect arg);
protected:
void reset() override;
void initialize(int gIdx, int pIdx) override;
void commit(int gIdx, int pIdx) override;
QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;
bool prepareNextFrame(QSGNode**);
void buildParticleNodes(QSGNode**);
void sceneGraphInvalidated() override;
private Q_SLOTS:
void createEngine(); //### method invoked by sprite list changing (in engine.h) - pretty nasty
void spriteAdvance(int spriteIndex);
void spritesUpdate(qreal time = 0 );
void mainThreadFetchImageData();
void invalidateSceneGraph();
private:
struct ImageData {
QUrl source;
QQuickPixmap pix;
};
QScopedPointer<ImageData> m_image;
QScopedPointer<ImageData> m_colorTable;
QScopedPointer<ImageData> m_sizeTable;
QScopedPointer<ImageData> m_opacityTable;
bool loadingSomething();
void finishBuildParticleNodes(QSGNode **n);
QColor m_color;
qreal m_color_variation;
QSGNode *m_outgoingNode;
QHash<int, QSGGeometryNode *> m_nodes;
QHash<int, int> m_idxStarts;//TODO: Proper resizing will lead to needing a spriteEngine per particle - do this after sprite engine gains transparent sharing?
QList<std::pair<int, int> > m_startsIdx;//Same data, optimized for alternate retrieval
int m_lastIdxStart;
QSGMaterial *m_material;
// derived values...
qreal m_alphaVariation;
qreal m_alpha;
qreal m_redVariation;
qreal m_greenVariation;
qreal m_blueVariation;
qreal m_rotation;
qreal m_rotationVariation;
qreal m_rotationVelocity;
qreal m_rotationVelocityVariation;
bool m_autoRotation;
QQuickDirection* m_xVector;
QQuickDirection* m_yVector;
QList<QQuickSprite*> m_sprites;
QQuickSpriteEngine* m_spriteEngine;
bool m_spritesInterpolate;
bool m_explicitColor;
bool m_explicitRotation;
bool m_explicitDeformation;
bool m_explicitAnimation;
QHash<int, QList<QQuickParticleData*> > m_shadowData;
void clearShadows();
QQuickParticleData* getShadowDatum(QQuickParticleData* datum);
bool m_bypassOptimizations;
PerformanceLevel perfLevel;
PerformanceLevel m_targetPerfLevel;
void checkPerfLevel(PerformanceLevel level);
bool m_debugMode;
template<class Vertex>
void initTexCoords(Vertex* v, int count){
Vertex* end = v + count;
// Vertex coords between (0.0, 0.0) and (1.0, 1.0)
while (v < end){
v[0].tx = 0;
v[0].ty = 0;
v[1].tx = 255;
v[1].ty = 0;
v[2].tx = 0;
v[2].ty = 255;
v[3].tx = 255;
v[3].ty = 255;
v += 4;
}
}
ImageMaterialData *getState(QSGMaterial *m) {
return static_cast<ImageMaterial *>(m)->state();
}
EntryEffect m_entryEffect;
Status m_status;
int m_startedImageLoading;
QRhi *m_rhi;
bool m_apiChecked;
qreal m_dpr;
bool m_previousActive;
};
QT_END_NAMESPACE
#endif // QQUICKIMAGEPARTICLE_P_H
@@ -0,0 +1,342 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
#include "qquickitemparticle_p.h"
#include <QtQuick/qsgnode.h>
#include <QTimer>
#include <QQmlComponent>
#include <QDebug>
QT_BEGIN_NAMESPACE
/*!
\qmltype ItemParticle
\inqmlmodule QtQuick.Particles
\inherits ParticlePainter
\brief For specifying a delegate to paint particles.
\ingroup qtquick-particles
*/
/*!
\qmlmethod void QtQuick.Particles::ItemParticle::freeze(Item item)
Suspends the flow of time for the logical particle which \a item represents,
allowing you to control its movement.
*/
/*!
\qmlmethod void QtQuick.Particles::ItemParticle::unfreeze(Item item)
Restarts the flow of time for the logical particle which \a item represents,
allowing it to be moved by the particle system again.
*/
/*!
\qmlmethod void QtQuick.Particles::ItemParticle::take(Item item, bool prioritize)
Asks the ItemParticle to take over control of \a item positioning temporarily.
It will follow the movement of a logical particle when one is available.
By default items form a queue when waiting for a logical particle, but if
\a prioritize is \c true, then it will go immediately to the head of the
queue.
ItemParticle does not take ownership of the item, and will relinquish
control when the logical particle expires. Commonly at this point you will
want to put it back in the queue, you can do this with the below line in
the delegate definition:
\code
ItemParticle.onDetached: itemParticleInstance.take(delegateRootItem);
\endcode
or delete it, such as with the below line in the delegate definition:
\code
ItemParticle.onDetached: delegateRootItem.destroy();
\endcode
*/
/*!
\qmlmethod void QtQuick.Particles::ItemParticle::give(Item item)
Orders the ItemParticle to give you control of the \a item. It will cease
controlling it and the item will lose its association to the logical
particle.
*/
/*!
\qmlproperty bool QtQuick.Particles::ItemParticle::fade
If true, the item will automatically be faded in and out
at the ends of its lifetime. If false, you will have to
implement any entry effect yourself.
Default is true.
*/
/*!
\qmlproperty Component QtQuick.Particles::ItemParticle::delegate
An instance of the delegate will be created for every logical particle, and
moved along with it. As an alternative to using delegate, you can create
Item instances yourself and hand them to the ItemParticle to move using the
\l take method.
Any delegate instances created by ItemParticle will be destroyed when
the logical particle expires.
*/
QQuickItemParticle::QQuickItemParticle(QQuickItem *parent) :
QQuickParticlePainter(parent), m_fade(true), m_lastT(0), m_activeCount(0), m_delegate(nullptr)
{
setFlag(QQuickItem::ItemHasContents);
clock = new Clock(this);
connect(this, &QQuickItemParticle::systemChanged, this, &QQuickItemParticle::reconnectSystem);
connect(this, &QQuickItemParticle::parentChanged, this, &QQuickItemParticle::reconnectParent);
connect(this, &QQuickItemParticle::enabledChanged, this, &QQuickItemParticle::updateClock);
reconnectSystem(m_system);
reconnectParent(parent);
}
QQuickItemParticle::~QQuickItemParticle()
{
delete clock;
qDeleteAll(m_managed);
}
void QQuickItemParticle::freeze(QQuickItem* item)
{
m_stasis << item;
}
void QQuickItemParticle::unfreeze(QQuickItem* item)
{
m_stasis.remove(item);
}
void QQuickItemParticle::take(QQuickItem *item, bool prioritize)
{
if (prioritize)
m_pendingItems.push_front(item);
else
m_pendingItems.push_back(item);
}
void QQuickItemParticle::give(QQuickItem *item)
{
for (auto groupId : groupIds()) {
for (QQuickParticleData* data : std::as_const(m_system->groupData[groupId]->data)) {
if (data->delegate == item){
m_deletables << item;
data->delegate = nullptr;
m_system->groupData[groupId]->kill(data);
return;
}
}
}
}
void QQuickItemParticle::initialize(int gIdx, int pIdx)
{
Q_UNUSED(gIdx);
Q_UNUSED(pIdx);
}
void QQuickItemParticle::commit(int, int)
{
}
void QQuickItemParticle::processDeletables()
{
foreach (QQuickItem* item, m_deletables){
if (m_fade)
item->setOpacity(0.);
item->setVisible(false);
QQuickItemParticleAttached* mpa;
if ((mpa = qobject_cast<QQuickItemParticleAttached*>(qmlAttachedPropertiesObject<QQuickItemParticle>(item)))) {
if (mpa->m_parentItem != nullptr)
item->setParentItem(mpa->m_parentItem);
mpa->detach();
}
int idx = -1;
if ((idx = m_managed.indexOf(item)) != -1) {
m_managed.takeAt(idx);
delete item;
}
m_activeCount--;
}
m_deletables.clear();
}
void QQuickItemParticle::tick(int time)
{
Q_UNUSED(time);//only needed because QTickAnimationProxy expects one
processDeletables();
for (auto groupId : groupIds()) {
for (QQuickParticleData* d : std::as_const(m_system->groupData[groupId]->data)) {
if (!d->delegate && d->t != -1 && d->stillAlive(m_system)) {
QQuickItem* parentItem = nullptr;
if (!m_pendingItems.isEmpty()){
QQuickItem *item = m_pendingItems.front();
m_pendingItems.pop_front();
parentItem = item->parentItem();
d->delegate = item;
}else if (m_delegate){
d->delegate = qobject_cast<QQuickItem*>(m_delegate->create(qmlContext(this)));
if (d->delegate)
m_managed << d->delegate;
}
if (d && d->delegate){//###Data can be zero if creating an item leads to a reset - this screws things up.
d->delegate->setX(d->curX(m_system) - d->delegate->width() / 2); //TODO: adjust for system?
d->delegate->setY(d->curY(m_system) - d->delegate->height() / 2);
QQuickItemParticleAttached* mpa = qobject_cast<QQuickItemParticleAttached*>(qmlAttachedPropertiesObject<QQuickItemParticle>(d->delegate));
if (mpa){
mpa->m_parentItem = parentItem;
mpa->m_mp = this;
mpa->attach();
}
d->delegate->setParentItem(this);
if (m_fade)
d->delegate->setOpacity(0.);
d->delegate->setVisible(false);//Will be set to true when we prepare the next frame
m_activeCount++;
}
}
}
}
}
void QQuickItemParticle::reset()
{
QQuickParticlePainter::reset();
// delete all managed items which had their logical particles cleared
// but leave it alone if the logical particle is maintained
QSet<QQuickItem*> lost = QSet<QQuickItem*>(m_managed.cbegin(), m_managed.cend());
for (auto groupId : groupIds()) {
for (QQuickParticleData* d : std::as_const(m_system->groupData[groupId]->data)) {
lost.remove(d->delegate);
}
}
m_deletables.unite(lost);
//TODO: This doesn't yet handle calling detach on taken particles in the system reset case
processDeletables();
}
QSGNode* QQuickItemParticle::updatePaintNode(QSGNode* n, UpdatePaintNodeData* d)
{
//Dummy update just to get painting tick
if (m_pleaseReset)
m_pleaseReset = false;
if (clockShouldUpdate()) {
prepareNextFrame();
update(); //Get called again
}
if (n)
n->markDirty(QSGNode::DirtyMaterial);
return QQuickItem::updatePaintNode(n,d);
}
void QQuickItemParticle::prepareNextFrame()
{
if (!m_system)
return;
qint64 timeStamp = m_system->systemSync(this);
qreal curT = timeStamp/1000.0;
qreal dt = curT - m_lastT;
m_lastT = curT;
if (!m_activeCount)
return;
//TODO: Size, better fade?
for (auto groupId : groupIds()) {
for (QQuickParticleData* data : std::as_const(m_system->groupData[groupId]->data)) {
QQuickItem* item = data->delegate;
if (!item)
continue;
float t = ((timeStamp / 1000.0f) - data->t) / data->lifeSpan;
if (m_stasis.contains(item)) {
data->t += dt;//Stasis effect
continue;
}
if (t >= 1.0f){//Usually happens from load
m_deletables << item;
data->delegate = nullptr;
}else{//Fade
data->delegate->setVisible(true);
if (m_fade){
float o = 1.f;
if (t <0.2f)
o = t * 5;
if (t > 0.8f)
o = (1-t)*5;
item->setOpacity(o);
}
}
item->setX(data->curX(m_system) - item->width() / 2 - m_systemOffset.x());
item->setY(data->curY(m_system) - item->height() / 2 - m_systemOffset.y());
}
}
}
QQuickItemParticleAttached *QQuickItemParticle::qmlAttachedProperties(QObject *object)
{
return new QQuickItemParticleAttached(object);
}
bool QQuickItemParticle::clockShouldUpdate() const
{
QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent());
return (m_system && m_system->isRunning() && !m_system->isPaused() && m_system->isEnabled()
&& ((parentItem && parentItem->isEnabled()) || !parentItem) && isEnabled());
}
void QQuickItemParticle::reconnectParent(QQuickItem *parentItem)
{
updateClock();
disconnect(m_parentEnabledStateConnection);
if (parentItem) {
m_parentEnabledStateConnection = connect(parentItem, &QQuickParticleSystem::enabledChanged,
this, &QQuickItemParticle::updateClock);
}
}
void QQuickItemParticle::reconnectSystem(QQuickParticleSystem *system)
{
updateClock();
disconnect(m_systemRunStateConnection);
disconnect(m_systemPauseStateConnection);
disconnect(m_systemEnabledStateConnection);
if (system) {
m_systemRunStateConnection = connect(m_system, &QQuickParticleSystem::runningChanged, this, [this](){
QQuickItemParticle::updateClock();
});
m_systemPauseStateConnection = connect(m_system, &QQuickParticleSystem::pausedChanged, this, [this](){
QQuickItemParticle::updateClock();
});
m_systemEnabledStateConnection = connect(m_system, &QQuickParticleSystem::enabledChanged, this,
&QQuickItemParticle::updateClock);
}
}
void QQuickItemParticle::updateClock()
{
if (clockShouldUpdate()) {
if (!clock->isRunning())
clock->start();
} else {
if (clock->isRunning())
clock->pause();
}
}
QT_END_NAMESPACE
#include "moc_qquickitemparticle_p.cpp"
@@ -0,0 +1,120 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef ITEMPARTICLE_H
#define ITEMPARTICLE_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticlepainter_p.h"
#include <QPointer>
#include <QSet>
#include <private/qquickanimation_p_p.h>
QT_BEGIN_NAMESPACE
class QQuickItemParticleAttached;
class Q_QUICKPARTICLES_EXPORT QQuickItemParticle : public QQuickParticlePainter
{
Q_OBJECT
Q_PROPERTY(bool fade READ fade WRITE setFade NOTIFY fadeChanged)
Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
QML_NAMED_ELEMENT(ItemParticle)
QML_ADDED_IN_VERSION(2, 0)
QML_ATTACHED(QQuickItemParticleAttached)
public:
explicit QQuickItemParticle(QQuickItem *parent = nullptr);
~QQuickItemParticle();
bool fade() const { return m_fade; }
QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;
static QQuickItemParticleAttached *qmlAttachedProperties(QObject *object);
QQmlComponent* delegate() const
{
return m_delegate;
}
Q_SIGNALS:
void fadeChanged();
void delegateChanged(QQmlComponent* arg);
public Q_SLOTS:
//TODO: Add a follow mode, where moving the delegate causes the logical particle to go with it?
void freeze(QQuickItem* item);
void unfreeze(QQuickItem* item);
void take(QQuickItem* item,bool prioritize=false);//take by modelparticle
void give(QQuickItem* item);//give from modelparticle
void setFade(bool arg){if (arg == m_fade) return; m_fade = arg; Q_EMIT fadeChanged();}
void setDelegate(QQmlComponent* arg)
{
if (m_delegate != arg) {
m_delegate = arg;
Q_EMIT delegateChanged(arg);
}
}
protected:
void reset() override;
void commit(int gIdx, int pIdx) override;
void initialize(int gIdx, int pIdx) override;
void prepareNextFrame();
private:
bool clockShouldUpdate() const;
void updateClock();
void reconnectSystem(QQuickParticleSystem *system);
void reconnectParent(QQuickItem *parent);
void processDeletables();
void tick(int time = 0);
QSet<QQuickItem* > m_deletables;
QList<QQuickItem* > m_managed;
bool m_fade;
QList<QQuickItem*> m_pendingItems;
QSet<QQuickItem*> m_stasis;
qreal m_lastT;
int m_activeCount;
QQmlComponent* m_delegate;
typedef QTickAnimationProxy<QQuickItemParticle, &QQuickItemParticle::tick> Clock;
Clock *clock;
QMetaObject::Connection m_systemRunStateConnection;
QMetaObject::Connection m_systemPauseStateConnection;
QMetaObject::Connection m_systemEnabledStateConnection;
QMetaObject::Connection m_parentEnabledStateConnection;
};
class QQuickItemParticleAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(QQuickItemParticle* particle READ particle CONSTANT FINAL);
public:
QQuickItemParticleAttached(QObject* parent)
: QObject(parent), m_mp(0), m_parentItem(nullptr)
{;}
QQuickItemParticle* particle() const { return m_mp; }
void detach(){Q_EMIT detached();}
void attach(){Q_EMIT attached();}
private:
QQuickItemParticle* m_mp;
QPointer<QQuickItem> m_parentItem;
friend class QQuickItemParticle;
Q_SIGNALS:
void detached();
void attached();
};
QT_END_NAMESPACE
#endif // ITEMPARTICLE_H
@@ -0,0 +1,54 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquicklineextruder_p.h"
#include <QRandomGenerator>
QT_BEGIN_NAMESPACE
/*!
\qmltype LineShape
\nativetype QQuickLineExtruder
\inqmlmodule QtQuick.Particles
\inherits ParticleExtruder
\brief Represents a line for affectors and emitters.
\ingroup qtquick-particles
*/
/*!
\qmlproperty bool QtQuick.Particles::LineShape::mirrored
By default, the line goes from (0,0) to (width, height) of the item that
this shape is being applied to.
If mirrored is set to true, this will be mirrored along the y axis.
The line will then go from (0,height) to (width, 0).
*/
QQuickLineExtruder::QQuickLineExtruder(QObject *parent) :
QQuickParticleExtruder(parent), m_mirrored(false)
{
}
QPointF QQuickLineExtruder::extrude(const QRectF &r)
{
qreal x,y;
if (!r.height()){
x = r.width() * QRandomGenerator::global()->generateDouble();
y = 0;
}else{
y = r.height() * QRandomGenerator::global()->generateDouble();
if (!r.width()){
x = 0;
}else{
x = r.width()/r.height() * y;
if (m_mirrored)
x = r.width() - x;
}
}
return QPointF(x,y);
}
QT_END_NAMESPACE
#include "moc_qquicklineextruder_p.cpp"
@@ -0,0 +1,56 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef LINEEXTRUDER_H
#define LINEEXTRUDER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleextruder_p.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickLineExtruder : public QQuickParticleExtruder
{
Q_OBJECT
//Default is topleft to bottom right. Flipped makes it topright to bottom left
Q_PROPERTY(bool mirrored READ mirrored WRITE setMirrored NOTIFY mirroredChanged)
QML_NAMED_ELEMENT(LineShape)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickLineExtruder(QObject *parent = nullptr);
QPointF extrude(const QRectF &) override;
bool mirrored() const
{
return m_mirrored;
}
Q_SIGNALS:
void mirroredChanged(bool arg);
public Q_SLOTS:
void setMirrored(bool arg)
{
if (m_mirrored != arg) {
m_mirrored = arg;
Q_EMIT mirroredChanged(arg);
}
}
private:
bool m_mirrored;
};
QT_END_NAMESPACE
#endif // LINEEXTRUDER_H
@@ -0,0 +1,126 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickmaskextruder_p.h"
#include <QtQml/qqml.h>
#include <QtQml/qqmlinfo.h>
#include <QtQml/qqmlcontext.h>
#include <QImage>
#include <QDebug>
#include <QRandomGenerator>
QT_BEGIN_NAMESPACE
/*!
\qmltype MaskShape
\nativetype QQuickMaskExtruder
\inqmlmodule QtQuick.Particles
\inherits Shape
\brief For representing an image as a shape to affectors and emitters.
\ingroup qtquick-particles
*/
/*!
\qmlproperty url QtQuick.Particles::MaskShape::source
The image to use as the mask. Areas with non-zero opacity
will be considered inside the shape.
*/
QQuickMaskExtruder::QQuickMaskExtruder(QObject *parent) :
QQuickParticleExtruder(parent)
, m_lastWidth(-1)
, m_lastHeight(-1)
{
}
void QQuickMaskExtruder::setSource(const QUrl &arg)
{
if (m_source != arg) {
m_source = arg;
m_lastHeight = -1;//Trigger reset
m_lastWidth = -1;
emit sourceChanged(m_source);
startMaskLoading();
}
}
void QQuickMaskExtruder::startMaskLoading()
{
m_pix.clear(this);
if (m_source.isEmpty())
return;
const QQmlContext *context = qmlContext(this);
m_pix.load(context->engine(), context->resolvedUrl(m_source));
if (m_pix.isLoading())
m_pix.connectFinished(this, SLOT(finishMaskLoading()));
else
finishMaskLoading();
}
void QQuickMaskExtruder::finishMaskLoading()
{
if (m_pix.isError())
qmlWarning(this) << m_pix.error();
}
QPointF QQuickMaskExtruder::extrude(const QRectF &r)
{
ensureInitialized(r);
if (!m_mask.size() || m_img.isNull())
return r.topLeft();
const QPointF p = m_mask[QRandomGenerator::global()->bounded(m_mask.size())];
//### Should random sub-pixel positioning be added?
return p + r.topLeft();
}
bool QQuickMaskExtruder::contains(const QRectF &bounds, const QPointF &point)
{
ensureInitialized(bounds);//###Current usage patterns WILL lead to different bounds/r calls. Separate list?
if (m_img.isNull())
return false;
QPointF pt = point - bounds.topLeft();
QPoint p(pt.x() * m_img.width() / bounds.width(),
pt.y() * m_img.height() / bounds.height());
return m_img.rect().contains(p) && (m_img.pixel(p) & 0xff000000);
}
void QQuickMaskExtruder::ensureInitialized(const QRectF &rf)
{
// Convert to integer coords to avoid comparing floats and ints which would
// often result in rounding errors.
QRect r = rf.toRect();
if (m_lastWidth == r.width() && m_lastHeight == r.height())
return;//Same as before
if (!m_pix.isReady())
return;
m_lastWidth = r.width();
m_lastHeight = r.height();
m_mask.clear();
m_img = m_pix.image();
// Image will in all likelyhood be in this format already, so
// no extra memory or conversion takes place
if (m_img.format() != QImage::Format_ARGB32 && m_img.format() != QImage::Format_ARGB32_Premultiplied)
m_img = std::move(m_img).convertToFormat(QImage::Format_ARGB32_Premultiplied);
// resample on the fly using 16-bit
int sx = (m_img.width() << 16) / r.width();
int sy = (m_img.height() << 16) / r.height();
int w = r.width();
int h = r.height();
for (int y=0; y<h; ++y) {
const uint *sl = (const uint *) m_img.constScanLine((y * sy) >> 16);
for (int x=0; x<w; ++x) {
if (sl[(x * sx) >> 16] & 0xff000000)
m_mask << QPointF(x, y);
}
}
}
QT_END_NAMESPACE
#include "moc_qquickmaskextruder_p.cpp"
@@ -0,0 +1,65 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef MASKEXTRUDER_H
#define MASKEXTRUDER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleextruder_p.h"
#include <private/qquickpixmap_p.h>
#include <QUrl>
#include <QImage>
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickMaskExtruder : public QQuickParticleExtruder
{
Q_OBJECT
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
QML_NAMED_ELEMENT(MaskShape)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickMaskExtruder(QObject *parent = nullptr);
QPointF extrude(const QRectF &) override;
bool contains(const QRectF &bounds, const QPointF &point) override;
QUrl source() const
{
return m_source;
}
Q_SIGNALS:
void sourceChanged(const QUrl &arg);
public Q_SLOTS:
void setSource(const QUrl &arg);
private Q_SLOTS:
void startMaskLoading();
void finishMaskLoading();
private:
QUrl m_source;
void ensureInitialized(const QRectF &r);
int m_lastWidth;
int m_lastHeight;
QQuickPixmap m_pix;
QImage m_img;
QList<QPointF> m_mask;//TODO: More memory efficient datastructures
//Perhaps just the mask for the largest bounds is stored, and interpolate up
};
QT_END_NAMESPACE
#endif // MASKEXTRUDER_H
@@ -0,0 +1,254 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
#include "qquickparticleaffector_p.h"
#include <QDebug>
#include <private/qqmlglobal_p.h>
QT_BEGIN_NAMESPACE
/*!
\qmltype ParticleAffector
//! \nativetype QQuickParticleAffector
\inqmlmodule QtQuick.Particles
\brief Applies alterations to the attributes of logical particles at any
point in their lifetime.
\ingroup qtquick-particles
The base ParticleAffector does not alter any attributes, but can be used to emit a signal
when a particle meets certain conditions.
If an affector has a defined size, then it will only affect particles within its size and
position on screen.
Affectors have different performance characteristics to the other particle system elements. In
particular, they have some simplifications to try to maintain a simulation at real-time or faster.
When running a system with Affectors, irregular frame timings that grow too large ( > one second per
frame) will cause the Affectors to try and cut corners with a faster but less accurate simulation.
If the system has multiple affectors the order in which they are applied is not guaranteed, and when
simulating larger time shifts they will simulate the whole shift each, which can lead to different
results compared to smaller time shifts.
Accurate simulation for large numbers of particles (hundreds) with multiple affectors may be
possible on some hardware, but on less capable hardware you should expect small irregularties in the
simulation as simulates with worse granularity.
*/
/*!
\qmlproperty ParticleSystem QtQuick.Particles::ParticleAffector::system
This is the system which will be affected by the element.
If the ParticleAffector is a direct child of a ParticleSystem, it will automatically be associated with
it.
*/
/*!
\qmlproperty list<string> QtQuick.Particles::ParticleAffector::groups
Which logical particle groups will be affected.
If empty, it will affect all particles.
*/
/*!
\qmlproperty list<string> QtQuick.Particles::ParticleAffector::whenCollidingWith
If any logical particle groups are specified here, then the particle affector
will only be triggered if the particle being examined intersects with
a particle of one of these groups.
This is different from the groups property. The groups property selects which
particles might be examined, and if they meet other criteria (including being
within the bounds of the ParticleAffector, modified by shape) then they will be tested
again to see if they intersect with a particles from one of the particle groups
in whenCollidingWith.
By default, no groups are specified.
*/
/*!
\qmlproperty bool QtQuick.Particles::ParticleAffector::enabled
If enabled is set to false, this affector will not affect any particles.
Usually this is used to conditionally turn an affector on or off.
Default value is true.
*/
/*!
\qmlproperty bool QtQuick.Particles::ParticleAffector::once
If once is set to true, this affector will only affect each particle
once in their lifetimes. If the affector normally simulates a continuous
effect over time, then it will simulate the effect of one second of time
the one instant it affects the particle.
Default value is false.
*/
/*!
\qmlproperty Shape QtQuick.Particles::ParticleAffector::shape
If a size has been defined, the shape property can be used to affect a
non-rectangular area.
*/
/*!
\qmlsignal QtQuick.Particles::ParticleAffector::affected(real x, real y)
This signal is emitted when a particle is selected to be affected. It will not be emitted
if a particle is considered by the ParticleAffector but not actually altered in any way.
In the special case where an ParticleAffector has no possible effect (e.g. ParticleAffector {}), this signal
will be emitted for all particles being considered if you connect to it. This allows you to
execute arbitrary code in response to particles (use the ParticleAffector::onAffectParticles
signal handler if you want to execute code which affects the particles
themselves). As this executes JavaScript code per particle, it is not recommended to use this
signal with a high-volume particle system.
(\a {x}, \a {y}) is the particle's current position.
*/
QQuickParticleAffector::QQuickParticleAffector(QQuickItem *parent) :
QQuickItem(parent), m_needsReset(false), m_ignoresTime(false), m_onceOff(false), m_enabled(true)
, m_system(nullptr), m_updateIntSet(false), m_shape(new QQuickParticleExtruder(this))
{
}
bool QQuickParticleAffector::isAffectedConnected()
{
IS_SIGNAL_CONNECTED(this, QQuickParticleAffector, affected, (qreal,qreal));
}
void QQuickParticleAffector::componentComplete()
{
if (!m_system && qobject_cast<QQuickParticleSystem*>(parentItem()))
setSystem(qobject_cast<QQuickParticleSystem*>(parentItem()));
QQuickItem::componentComplete();
}
bool QQuickParticleAffector::activeGroup(int g) {
if (!m_system)
return false;
if (m_updateIntSet){ //This can occur before group ids are properly assigned, but that resets the flag
m_groupIds.clear();
foreach (const QString &p, m_groups)
m_groupIds << m_system->groupIds[p];
m_updateIntSet = false;
}
return m_groupIds.isEmpty() || m_groupIds.contains(g);
}
bool QQuickParticleAffector::shouldAffect(QQuickParticleData* d)
{
if (!d)
return false;
if (!m_system)
return false;
if (activeGroup(d->groupId)){
if ((m_onceOff && m_onceOffed.contains(std::make_pair(d->groupId, d->index)))
|| !d->stillAlive(m_system))
return false;
//Need to have previous location for affected anyways
if (width() == 0 || height() == 0
|| m_shape->contains(QRectF(m_offset.x(), m_offset.y(), width(), height()), QPointF(d->curX(m_system), d->curY(m_system)))){
if (m_whenCollidingWith.isEmpty() || isColliding(d)){
return true;
}
}
}
return false;
}
void QQuickParticleAffector::postAffect(QQuickParticleData* d)
{
if (!m_system)
return;
m_system->needsReset << d;
if (m_onceOff)
m_onceOffed << std::make_pair(d->groupId, d->index);
if (isAffectedConnected())
emit affected(d->curX(m_system), d->curY(m_system));
}
const qreal QQuickParticleAffector::simulationDelta = 0.020;
const qreal QQuickParticleAffector::simulationCutoff = 1.000;//If this goes above 1.0, then m_once behaviour needs special codepath
void QQuickParticleAffector::affectSystem(qreal dt)
{
if (!m_enabled)
return;
if (!m_system)
return;
//If not reimplemented, calls affectParticle per particle
//But only on particles in targeted system/area
updateOffsets();//### Needed if an ancestor is transformed.
if (m_onceOff)
dt = 1.0;
for (QQuickParticleGroupData* gd : std::as_const(m_system->groupData)) {
if (activeGroup(gd->index)) {
for (QQuickParticleData* d : std::as_const(gd->data)) {
if (shouldAffect(d)) {
bool affected = false;
qreal myDt = dt;
if (!m_ignoresTime && myDt < simulationCutoff) {
int realTime = m_system->timeInt;
m_system->timeInt -= myDt * 1000.0;
while (myDt > simulationDelta) {
m_system->timeInt += simulationDelta * 1000.0;
if (d->alive(m_system))//Only affect during the parts it was alive for
affected = affectParticle(d, simulationDelta) || affected;
myDt -= simulationDelta;
}
m_system->timeInt = realTime;
}
if (myDt > 0.0)
affected = affectParticle(d, myDt) || affected;
if (affected)
postAffect(d);
}
}
}
}
}
bool QQuickParticleAffector::affectParticle(QQuickParticleData *, qreal )
{
return true;
}
void QQuickParticleAffector::reset(QQuickParticleData* pd)
{//TODO: This, among other ones, should be restructured so they don't all need to remember to call the superclass
if (m_onceOff)
if (activeGroup(pd->groupId))
m_onceOffed.remove(std::make_pair(pd->groupId, pd->index));
}
void QQuickParticleAffector::updateOffsets()
{
if (m_system)
m_offset = m_system->mapFromItem(this, QPointF(0, 0));
}
bool QQuickParticleAffector::isColliding(QQuickParticleData *d) const
{
if (!m_system)
return false;
qreal myCurX = d->curX(m_system);
qreal myCurY = d->curY(m_system);
qreal myCurSize = d->curSize(m_system) / 2;
foreach (const QString &group, m_whenCollidingWith){
foreach (QQuickParticleData* other, m_system->groupData[m_system->groupIds[group]]->data){
if (!other->stillAlive(m_system))
continue;
qreal otherCurX = other->curX(m_system);
qreal otherCurY = other->curY(m_system);
qreal otherCurSize = other->curSize(m_system) / 2;
if ((myCurX + myCurSize > otherCurX - otherCurSize
&& myCurX - myCurSize < otherCurX + otherCurSize)
&& (myCurY + myCurSize > otherCurY - otherCurSize
&& myCurY - myCurSize < otherCurY + otherCurSize))
return true;
}
}
return false;
}
QT_END_NAMESPACE
#include "moc_qquickparticleaffector_p.cpp"
@@ -0,0 +1,177 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef PARTICLEAFFECTOR_H
#define PARTICLEAFFECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QObject>
#include <QSet>
#include "qquickparticlesystem_p.h"
#include "qquickparticleextruder_p.h"
#include "qtquickparticlesglobal_p.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickParticleAffector : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QQuickParticleSystem* system READ system WRITE setSystem NOTIFY systemChanged)
Q_PROPERTY(QStringList groups READ groups WRITE setGroups NOTIFY groupsChanged)
Q_PROPERTY(QStringList whenCollidingWith READ whenCollidingWith WRITE setWhenCollidingWith NOTIFY whenCollidingWithChanged)
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(bool once READ onceOff WRITE setOnceOff NOTIFY onceChanged)
Q_PROPERTY(QQuickParticleExtruder* shape READ shape WRITE setShape NOTIFY shapeChanged)
QML_NAMED_ELEMENT(ParticleAffector)
QML_ADDED_IN_VERSION(2, 0)
QML_UNCREATABLE("Abstract type. Use one of the inheriting types instead.")
public:
explicit QQuickParticleAffector(QQuickItem *parent = nullptr);
virtual void affectSystem(qreal dt);
virtual void reset(QQuickParticleData*);//As some store their own data per particle?
QQuickParticleSystem* system() const
{
return m_system;
}
QStringList groups() const
{
return m_groups;
}
bool enabled() const
{
return m_enabled;
}
bool onceOff() const
{
return m_onceOff;
}
QQuickParticleExtruder* shape() const
{
return m_shape;
}
QStringList whenCollidingWith() const
{
return m_whenCollidingWith;
}
Q_SIGNALS:
void systemChanged(QQuickParticleSystem* arg);
void groupsChanged(const QStringList &arg);
void enabledChanged(bool arg);
void onceChanged(bool arg);
void shapeChanged(QQuickParticleExtruder* arg);
void affected(qreal x, qreal y);
void whenCollidingWithChanged(const QStringList &arg);
public Q_SLOTS:
void setSystem(QQuickParticleSystem* arg)
{
if (m_system != arg) {
m_system = arg;
if (m_system)
m_system->registerParticleAffector(this);
Q_EMIT systemChanged(arg);
}
}
void setGroups(const QStringList &arg)
{
if (m_groups != arg) {
m_groups = arg;
m_updateIntSet = true;
Q_EMIT groupsChanged(arg);
}
}
void setEnabled(bool arg)
{
if (m_enabled != arg) {
m_enabled = arg;
Q_EMIT enabledChanged(arg);
}
}
void setOnceOff(bool arg)
{
if (m_onceOff != arg) {
m_onceOff = arg;
m_needsReset = true;
Q_EMIT onceChanged(arg);
}
}
void setShape(QQuickParticleExtruder* arg)
{
if (m_shape != arg) {
m_shape = arg;
Q_EMIT shapeChanged(arg);
}
}
void setWhenCollidingWith(const QStringList &arg)
{
if (m_whenCollidingWith != arg) {
m_whenCollidingWith = arg;
Q_EMIT whenCollidingWithChanged(arg);
}
}
public Q_SLOTS:
void updateOffsets();
protected:
friend class QQuickParticleSystem;
virtual bool affectParticle(QQuickParticleData *d, qreal dt);
bool m_needsReset:1;//### What is this really saving?
bool m_ignoresTime:1;
bool m_onceOff:1;
bool m_enabled:1;
QQuickParticleSystem* m_system;
QStringList m_groups;
bool activeGroup(int g);
bool shouldAffect(QQuickParticleData* datum);//Call to do the logic on whether it is affecting that datum
void postAffect(QQuickParticleData* datum);//Call to do the post-affect logic on particles which WERE affected(once off, needs reset, affected signal)
void componentComplete() override;
bool isAffectedConnected();
static const qreal simulationDelta;
static const qreal simulationCutoff;
QPointF m_offset;
QSet<std::pair<int, int>> m_onceOffed;
private:
QSet<int> m_groupIds;
bool m_updateIntSet;
QQuickParticleExtruder* m_shape;
QStringList m_whenCollidingWith;
bool isColliding(QQuickParticleData* d) const;
};
QT_END_NAMESPACE
#endif // PARTICLEAFFECTOR_H
@@ -0,0 +1,476 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
#include "qquickparticleemitter_p.h"
#include <private/qqmlglobal_p.h>
#include <private/qquickv4particledata_p.h>
#include <QtCore/qrandom.h>
QT_BEGIN_NAMESPACE
/*!
\qmltype Emitter
//! \nativetype QQuickParticleEmitter
\inqmlmodule QtQuick.Particles
\brief Emits logical particles.
\ingroup qtquick-particles
This element emits logical particles into the ParticleSystem, with the
given starting attributes.
Note that logical particles are not
automatically rendered, you will need to have one or more
ParticlePainter elements visualizing them.
Note that the given starting attributes can be modified at any point
in the particle's lifetime by any Affector element in the same
ParticleSystem. This includes attributes like lifespan.
*/
/*!
\qmlproperty ParticleSystem QtQuick.Particles::Emitter::system
This is the Particle system that the Emitter will emit into.
This can be omitted if the Emitter is a direct child of the ParticleSystem
*/
/*!
\qmlproperty string QtQuick.Particles::Emitter::group
This is the logical particle group which it will emit into.
Default value is "" (empty string).
*/
/*!
\qmlproperty Shape QtQuick.Particles::Emitter::shape
This shape is applied with the size of the Emitter. Particles will be emitted
randomly from any area covered by the shape.
The default shape is a filled in rectangle, which corresponds to the full bounding
box of the Emitter.
*/
/*!
\qmlproperty bool QtQuick.Particles::Emitter::enabled
If set to false, the emitter will cease emissions until it is set to true.
Default value is true.
*/
/*!
\qmlproperty real QtQuick.Particles::Emitter::emitRate
Number of particles emitted per second.
Default value is 10 particles per second.
*/
/*!
\qmlproperty int QtQuick.Particles::Emitter::lifeSpan
The time in milliseconds each emitted particle should last for.
If you do not want particles to automatically die after a time, for example if
you wish to dispose of them manually, set lifeSpan to Emitter.InfiniteLife.
lifeSpans greater than or equal to 600000 (10 minutes) will be treated as infinite.
Particles with lifeSpans less than or equal to 0 will start out dead.
Default value is 1000 (one second).
*/
/*!
\qmlproperty int QtQuick.Particles::Emitter::lifeSpanVariation
Particle lifespans will vary by up to this much in either direction.
Default value is 0.
*/
/*!
\qmlproperty int QtQuick.Particles::Emitter::maximumEmitted
The maximum number of particles at a time that this emitter will have alive.
This can be set as a performance optimization (when using burst and pulse) or
to stagger emissions.
If this is set to a number below zero, then there is no maximum limit on the number
of particles this emitter can have alive.
The default value is -1.
*/
/*!
\qmlproperty int QtQuick.Particles::Emitter::startTime
If this value is set when the emitter is loaded, then it will emit particles from the
past, up to startTime milliseconds ago. These will simulate as if they were emitted then,
but will not have any affectors applied to them. Affectors will take effect from the present time.
*/
/*!
\qmlproperty real QtQuick.Particles::Emitter::size
The size in pixels of the particles at the start of their life.
Default value is 16.
*/
/*!
\qmlproperty real QtQuick.Particles::Emitter::endSize
The size in pixels of the particles at the end of their life. Size will
be linearly interpolated during the life of the particle from this value and
size. If endSize is -1, then the size of the particle will remain constant at
the starting size.
Default value is -1.
*/
/*!
\qmlproperty real QtQuick.Particles::Emitter::sizeVariation
The size of a particle can vary by this much up or down from size/endSize. The same
random addition is made to both size and endSize for a single particle.
Default value is 0.
*/
/*!
\qmlproperty StochasticDirection QtQuick.Particles::Emitter::velocity
The starting velocity of the particles emitted.
*/
/*!
\qmlproperty StochasticDirection QtQuick.Particles::Emitter::acceleration
The starting acceleraton of the particles emitted.
*/
/*!
\qmlproperty real QtQuick.Particles::Emitter::velocityFromMovement
If this value is non-zero, then any movement of the emitter will provide additional
starting velocity to the particles based on the movement. The additional vector will be the
same angle as the emitter's movement, with a magnitude that is the magnitude of the emitters
movement multiplied by velocityFromMovement.
Default value is 0.
*/
/*!
\qmlsignal QtQuick.Particles::Emitter::emitParticles(Array particles)
This signal is emitted when particles are emitted. \a particles is a JavaScript
array of Particle objects. You can modify particle attributes directly within the handler.
\note JavaScript is slower to execute, so it is not recommended to use this in
high-volume particle systems.
*/
/*! \qmlmethod void QtQuick.Particles::Emitter::burst(int count)
Emits a number of particles, specified by \a count, from this emitter immediately.
*/
/*! \qmlmethod void QtQuick.Particles::Emitter::burst(int count, int x, int y)
Emits a number of particles, specified by \a count, from this emitter immediately.
The particles are emitted as if the Emitter was positioned at (\a {x}, \a {y}) but
all other properties are the same.
*/
/*! \qmlmethod void QtQuick.Particles::Emitter::pulse(int duration)
If the emitter is not enabled, enables it for a specified \a duration
(in milliseconds) and then switches it back off.
*/
QQuickParticleEmitter::QQuickParticleEmitter(QQuickItem *parent) :
QQuickItem(parent)
, m_particlesPerSecond(10)
, m_particleDuration(1000)
, m_particleDurationVariation(0)
, m_enabled(true)
, m_system(nullptr)
, m_extruder(nullptr)
, m_defaultExtruder(nullptr)
, m_velocity(&m_nullVector)
, m_acceleration(&m_nullVector)
, m_particleSize(16)
, m_particleEndSize(-1)
, m_particleSizeVariation(0)
, m_startTime(0)
, m_overwrite(true)
, m_pulseLeft(0)
, m_maxParticleCount(-1)
, m_velocity_from_movement(0)
, m_reset_last(true)
, m_last_timestamp(-1)
, m_last_emission(0)
, m_groupIdNeedRecalculation(false)
, m_groupId(QQuickParticleGroupData::DefaultGroupID)
{
//TODO: Reset velocity/acc back to null vector? Or allow null pointer?
connect(this, &QQuickParticleEmitter::particlesPerSecondChanged,
this, &QQuickParticleEmitter::particleCountChanged);
connect(this, &QQuickParticleEmitter::particleDurationChanged,
this, &QQuickParticleEmitter::particleCountChanged);
}
QQuickParticleEmitter::~QQuickParticleEmitter()
{
if (m_defaultExtruder)
delete m_defaultExtruder;
}
bool QQuickParticleEmitter::isEmitConnected()
{
IS_SIGNAL_CONNECTED(
this, QQuickParticleEmitter, emitParticles, (const QList<QQuickV4ParticleData> &));
}
void QQuickParticleEmitter::reclaculateGroupId() const
{
if (!m_system) {
m_groupId = QQuickParticleGroupData::InvalidID;
return;
}
m_groupId = m_system->groupIds.value(group(), QQuickParticleGroupData::InvalidID);
m_groupIdNeedRecalculation = m_groupId == QQuickParticleGroupData::InvalidID;
}
void QQuickParticleEmitter::componentComplete()
{
if (!m_system && qobject_cast<QQuickParticleSystem*>(parentItem()))
setSystem(qobject_cast<QQuickParticleSystem*>(parentItem()));
if (m_system)
m_system->finishRegisteringParticleEmitter(this);
QQuickItem::componentComplete();
}
void QQuickParticleEmitter::setEnabled(bool arg)
{
if (m_enabled != arg) {
m_enabled = arg;
emit enabledChanged(arg);
}
}
QQuickParticleExtruder* QQuickParticleEmitter::effectiveExtruder()
{
if (m_extruder)
return m_extruder;
if (!m_defaultExtruder)
m_defaultExtruder = new QQuickParticleExtruder;
return m_defaultExtruder;
}
void QQuickParticleEmitter::pulse(int milliseconds)
{
if (!m_enabled)
m_pulseLeft = milliseconds;
}
void QQuickParticleEmitter::burst(int num)
{
m_burstQueue << std::make_pair(num, QPointF(x(), y()));
}
void QQuickParticleEmitter::burst(int num, qreal x, qreal y)
{
m_burstQueue << std::make_pair(num, QPointF(x, y));
}
void QQuickParticleEmitter::setMaxParticleCount(int arg)
{
if (m_maxParticleCount != arg) {
if (arg < 0 && m_maxParticleCount >= 0){
connect(this, &QQuickParticleEmitter::particlesPerSecondChanged,
this, &QQuickParticleEmitter::particleCountChanged);
connect(this, &QQuickParticleEmitter::particleDurationChanged,
this, &QQuickParticleEmitter::particleCountChanged);
} else if (arg >= 0 && m_maxParticleCount < 0){
disconnect(this, &QQuickParticleEmitter::particlesPerSecondChanged,
this, &QQuickParticleEmitter::particleCountChanged);
disconnect(this, &QQuickParticleEmitter::particleDurationChanged,
this, &QQuickParticleEmitter::velocityFromMovementChanged);
}
m_overwrite = arg < 0;
m_maxParticleCount = arg;
emit maximumEmittedChanged(arg);
emit particleCountChanged();
}
}
void QQuickParticleEmitter::setVelocityFromMovement(qreal t)
{
if (t == m_velocity_from_movement)
return;
m_velocity_from_movement = t;
emit velocityFromMovementChanged();
}
void QQuickParticleEmitter::reset()
{
m_reset_last = true;
}
void QQuickParticleEmitter::emitWindow(int timeStamp)
{
if (m_system == nullptr)
return;
if ((!m_enabled || m_particlesPerSecond <= 0)&& !m_pulseLeft && m_burstQueue.isEmpty()){
m_reset_last = true;
return;
}
if (m_reset_last) {
m_last_emitter = m_last_last_emitter = QPointF(x(), y());
if (m_last_timestamp == -1)
m_last_timestamp = (timeStamp - m_startTime)/1000.;
else
m_last_timestamp = timeStamp/1000.;
m_last_emission = m_last_timestamp;
m_reset_last = false;
m_emitCap = -1;
}
if (m_pulseLeft){
m_pulseLeft -= timeStamp - m_last_timestamp * 1000.;
if (m_pulseLeft < 0){
if (!m_enabled)
timeStamp += m_pulseLeft;
m_pulseLeft = 0;
}
}
qreal time = timeStamp / 1000.;
qreal particleRatio = 1. / m_particlesPerSecond;
qreal pt = m_last_emission;
qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
pt = time - maxLife;
qreal opt = pt; // original particle time
qreal dt = time - m_last_timestamp; // timestamp delta...
if (!dt)
dt = 0.000001;
// emitter difference since last...
qreal dex = (x() - m_last_emitter.x());
qreal dey = (y() - m_last_emitter.y());
qreal ax = (m_last_last_emitter.x() + m_last_emitter.x()) / 2;
qreal bx = m_last_emitter.x();
qreal cx = (x() + m_last_emitter.x()) / 2;
qreal ay = (m_last_last_emitter.y() + m_last_emitter.y()) / 2;
qreal by = m_last_emitter.y();
qreal cy = (y() + m_last_emitter.y()) / 2;
qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
qreal emitter_x_offset = m_last_emitter.x() - x();
qreal emitter_y_offset = m_last_emitter.y() - y();
if (!m_burstQueue.isEmpty() && !m_pulseLeft && !m_enabled)//'outside time' emissions only
pt = time;
QList<QQuickParticleData*> toEmit;
while ((pt < time && m_emitCap) || !m_burstQueue.isEmpty()) {
//int pos = m_last_particle % m_particle_count;
QQuickParticleData* datum = m_system->newDatum(m_system->groupIds[m_group], !m_overwrite);
if (datum){//actually emit(otherwise we've been asked to skip this one)
qreal t = 1 - (pt - opt) / dt;
qreal vx =
- 2 * ax * (1 - t)
+ 2 * bx * (1 - 2 * t)
+ 2 * cx * t;
qreal vy =
- 2 * ay * (1 - t)
+ 2 * by * (1 - 2 * t)
+ 2 * cy * t;
// Particle timestamp
datum->t = pt;
datum->lifeSpan =
(m_particleDuration
+ (QRandomGenerator::global()->bounded((m_particleDurationVariation*2) + 1) - m_particleDurationVariation))
/ 1000.0;
if (datum->lifeSpan >= m_system->maxLife){
datum->lifeSpan = m_system->maxLife;
if (m_emitCap == -1)
m_emitCap = particleCount();
m_emitCap--;//emitCap keeps us from reemitting 'infinite' particles after their life. Unless you reset the emitter.
}
// Particle position
QRectF boundsRect;
if (!m_burstQueue.isEmpty()){
boundsRect = QRectF(m_burstQueue.first().second.x() - x(), m_burstQueue.first().second.y() - y(),
width(), height());
} else {
boundsRect = QRectF(emitter_x_offset + dex * (pt - opt) / dt, emitter_y_offset + dey * (pt - opt) / dt
, width(), height());
}
QPointF newPos = effectiveExtruder()->extrude(boundsRect);
datum->x = newPos.x();
datum->y = newPos.y();
// Particle velocity
const QPointF &velocity = m_velocity->sample(newPos);
datum->vx = velocity.x()
+ m_velocity_from_movement * vx;
datum->vy = velocity.y()
+ m_velocity_from_movement * vy;
// Particle acceleration
const QPointF &accel = m_acceleration->sample(newPos);
datum->ax = accel.x();
datum->ay = accel.y();
// Particle size
float sizeVariation = -m_particleSizeVariation
+ QRandomGenerator::global()->bounded(m_particleSizeVariation * 2);
float size = qMax((qreal)0.0 , m_particleSize + sizeVariation);
float endSize = qMax((qreal)0.0 , sizeAtEnd + sizeVariation);
datum->size = size;// * float(m_emitting);
datum->endSize = endSize;// * float(m_emitting);
toEmit << datum;
}
if (m_burstQueue.isEmpty()){
pt += particleRatio;
}else{
m_burstQueue.first().first--;
if (m_burstQueue.first().first <= 0)
m_burstQueue.pop_front();
}
}
foreach (QQuickParticleData* d, toEmit)
m_system->emitParticle(d, this);
if (isEmitConnected()) {
//Done after emitParticle so that the Painter::load is done first, this allows you to customize its static variables
//We then don't need to request another reload, because the first reload isn't scheduled until we get back to the render thread
QList<QQuickV4ParticleData> particles;
particles.reserve(toEmit.size());
for (QQuickParticleData *particle : std::as_const(toEmit))
particles.push_back(particle->v4Value(m_system));
emit emitParticles(particles);//A chance for arbitrary JS changes
}
m_last_emission = pt;
m_last_last_emitter = m_last_emitter;
m_last_emitter = QPointF(x(), y());
m_last_timestamp = time;
}
QT_END_NAMESPACE
#include "moc_qquickparticleemitter_p.cpp"
@@ -0,0 +1,340 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef PARTICLEEMITTER_H
#define PARTICLEEMITTER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtQuick/QQuickItem>
#include <QDebug>
#include "qquickparticlesystem_p.h"
#include "qquickparticleextruder_p.h"
#include "qquickdirection_p.h"
#include <QList>
#include <QPointF>
#include <utility>
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickParticleEmitter : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QQuickParticleSystem* system READ system WRITE setSystem NOTIFY systemChanged)
Q_PROPERTY(QString group READ group WRITE setGroup NOTIFY groupChanged)
Q_PROPERTY(QQuickParticleExtruder* shape READ extruder WRITE setExtruder NOTIFY extruderChanged)
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(int startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
Q_PROPERTY(qreal emitRate READ particlesPerSecond WRITE setParticlesPerSecond NOTIFY particlesPerSecondChanged)
Q_PROPERTY(int lifeSpan READ particleDuration WRITE setParticleDuration NOTIFY particleDurationChanged)
Q_PROPERTY(int lifeSpanVariation READ particleDurationVariation WRITE setParticleDurationVariation NOTIFY particleDurationVariationChanged)
Q_PROPERTY(int maximumEmitted READ maxParticleCount WRITE setMaxParticleCount NOTIFY maximumEmittedChanged)
Q_PROPERTY(qreal size READ particleSize WRITE setParticleSize NOTIFY particleSizeChanged)
Q_PROPERTY(qreal endSize READ particleEndSize WRITE setParticleEndSize NOTIFY particleEndSizeChanged)
Q_PROPERTY(qreal sizeVariation READ particleSizeVariation WRITE setParticleSizeVariation NOTIFY particleSizeVariationChanged)
Q_PROPERTY(QQuickDirection *velocity READ velocity WRITE setVelocity NOTIFY velocityChanged)
Q_PROPERTY(QQuickDirection *acceleration READ acceleration WRITE setAcceleration NOTIFY accelerationChanged)
Q_PROPERTY(qreal velocityFromMovement READ velocityFromMovement WRITE setVelocityFromMovement NOTIFY velocityFromMovementChanged)
QML_NAMED_ELEMENT(Emitter)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickParticleEmitter(QQuickItem *parent = nullptr);
virtual ~QQuickParticleEmitter();
virtual void emitWindow(int timeStamp);
enum Lifetime {
InfiniteLife = QQuickParticleSystem::maxLife
};
Q_ENUM(Lifetime)
bool enabled() const
{
return m_enabled;
}
qreal particlesPerSecond() const
{
return m_particlesPerSecond;
}
int particleDuration() const
{
return m_particleDuration;
}
QQuickParticleSystem* system() const
{
return m_system;
}
QString group() const
{
return m_group;
}
QQuickParticleGroupData::ID groupId() const
{
if (m_groupIdNeedRecalculation)
reclaculateGroupId();
return m_groupId;
}
int particleDurationVariation() const
{
return m_particleDurationVariation;
}
qreal velocityFromMovement() const { return m_velocity_from_movement; }
void setVelocityFromMovement(qreal s);
void componentComplete() override;
Q_SIGNALS:
void emitParticles(const QList<QQuickV4ParticleData> &particles);
void particlesPerSecondChanged(qreal);
void particleDurationChanged(int);
void enabledChanged(bool);
void systemChanged(QQuickParticleSystem* arg);
void groupChanged(const QString &arg);
void particleDurationVariationChanged(int arg);
void extruderChanged(QQuickParticleExtruder* arg);
void particleSizeChanged(qreal arg);
void particleEndSizeChanged(qreal arg);
void particleSizeVariationChanged(qreal arg);
void velocityChanged(QQuickDirection * arg);
void accelerationChanged(QQuickDirection * arg);
void maximumEmittedChanged(int arg);
void particleCountChanged();
void velocityFromMovementChanged();
void startTimeChanged(int arg);
public Q_SLOTS:
void pulse(int milliseconds);
void burst(int num);
void burst(int num, qreal x, qreal y);
void setEnabled(bool arg);
void setParticlesPerSecond(qreal arg)
{
if (m_particlesPerSecond != arg) {
m_particlesPerSecond = arg;
Q_EMIT particlesPerSecondChanged(arg);
}
}
void setParticleDuration(int arg)
{
if (m_particleDuration != arg) {
m_particleDuration = arg;
Q_EMIT particleDurationChanged(arg);
}
}
void setSystem(QQuickParticleSystem* arg)
{
if (m_system != arg) {
m_system = arg;
m_groupIdNeedRecalculation = true;
if (m_system)
m_system->registerParticleEmitter(this);
Q_EMIT systemChanged(arg);
}
}
void setGroup(const QString &arg)
{
if (m_group != arg) {
m_group = arg;
m_groupIdNeedRecalculation = true;
Q_EMIT groupChanged(arg);
}
}
void setParticleDurationVariation(int arg)
{
if (m_particleDurationVariation != arg) {
m_particleDurationVariation = arg;
Q_EMIT particleDurationVariationChanged(arg);
}
}
void setExtruder(QQuickParticleExtruder* arg)
{
if (m_extruder != arg) {
m_extruder = arg;
Q_EMIT extruderChanged(arg);
}
}
void setParticleSize(qreal arg)
{
if (m_particleSize != arg) {
m_particleSize = arg;
Q_EMIT particleSizeChanged(arg);
}
}
void setParticleEndSize(qreal arg)
{
if (m_particleEndSize != arg) {
m_particleEndSize = arg;
Q_EMIT particleEndSizeChanged(arg);
}
}
void setParticleSizeVariation(qreal arg)
{
if (m_particleSizeVariation != arg) {
m_particleSizeVariation = arg;
Q_EMIT particleSizeVariationChanged(arg);
}
}
void setVelocity(QQuickDirection * arg)
{
if (m_velocity != arg) {
m_velocity = arg;
Q_EMIT velocityChanged(arg);
}
}
void setAcceleration(QQuickDirection * arg)
{
if (m_acceleration != arg) {
m_acceleration = arg;
Q_EMIT accelerationChanged(arg);
}
}
void setMaxParticleCount(int arg);
void setStartTime(int arg)
{
if (m_startTime != arg) {
m_startTime = arg;
Q_EMIT startTimeChanged(arg);
}
}
virtual void reset();
public:
int particleCount() const
{
if (m_maxParticleCount >= 0)
return m_maxParticleCount;
return m_particlesPerSecond*((m_particleDuration+m_particleDurationVariation)/1000.0);
}
QQuickParticleExtruder* extruder() const
{
return m_extruder;
}
qreal particleSize() const
{
return m_particleSize;
}
qreal particleEndSize() const
{
return m_particleEndSize;
}
qreal particleSizeVariation() const
{
return m_particleSizeVariation;
}
QQuickDirection * velocity() const
{
return m_velocity;
}
QQuickDirection * acceleration() const
{
return m_acceleration;
}
int maxParticleCount() const
{
return m_maxParticleCount;
}
int startTime() const
{
return m_startTime;
}
void reclaculateGroupId() const;
protected:
qreal m_particlesPerSecond;
int m_particleDuration;
int m_particleDurationVariation;
bool m_enabled;
QQuickParticleSystem* m_system;
QQuickParticleExtruder* m_extruder;
QQuickParticleExtruder* m_defaultExtruder;
QQuickParticleExtruder* effectiveExtruder();
QQuickDirection * m_velocity;
QQuickDirection * m_acceleration;
qreal m_particleSize;
qreal m_particleEndSize;
qreal m_particleSizeVariation;
int m_startTime;
bool m_overwrite;
int m_pulseLeft;
QList<std::pair<int, QPointF > > m_burstQueue;
int m_maxParticleCount;
//Used in default implementation, but might be useful
qreal m_velocity_from_movement;
int m_emitCap;
bool m_reset_last;
qreal m_last_timestamp;
qreal m_last_emission;
QPointF m_last_emitter;
QPointF m_last_last_emitter;
bool isEmitConnected();
private: // data
QString m_group;
mutable bool m_groupIdNeedRecalculation;
mutable QQuickParticleGroupData::ID m_groupId;
QQuickDirection m_nullVector;
};
QT_END_NAMESPACE
#endif // PARTICLEEMITTER_H
@@ -0,0 +1,37 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickparticleextruder_p.h"
#include <QRandomGenerator>
QT_BEGIN_NAMESPACE
/*!
\qmltype ParticleExtruder
\nativetype QQuickParticleExtruder
\inqmlmodule QtQuick.Particles
\brief For specifying an area for affectors and emitters.
\ingroup qtquick-particles
The base class is just a rectangle.
*/
QQuickParticleExtruder::QQuickParticleExtruder(QObject *parent) :
QObject(parent)
{
}
QPointF QQuickParticleExtruder::extrude(const QRectF &rect)
{
return QPointF(QRandomGenerator::global()->generateDouble() * rect.width() + rect.x(),
QRandomGenerator::global()->generateDouble() * rect.height() + rect.y());
}
bool QQuickParticleExtruder::contains(const QRectF &bounds, const QPointF &point)
{
return bounds.contains(point);
}
QT_END_NAMESPACE
#include "moc_qquickparticleextruder_p.cpp"
@@ -0,0 +1,43 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef PARTICLEEXTRUDER_H
#define PARTICLEEXTRUDER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QObject>
#include <QRectF>
#include <QPointF>
#include <QtQml/qqml.h>
#include <QtCore/private/qglobal_p.h>
#include <QtQuickParticles/qtquickparticlesexports.h>
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickParticleExtruder : public QObject
{
Q_OBJECT
QML_NAMED_ELEMENT(ParticleExtruder)
QML_ADDED_IN_VERSION(2, 0)
QML_UNCREATABLE("Abstract type. Use one of the inheriting types instead.")
public:
explicit QQuickParticleExtruder(QObject *parent = nullptr);
virtual QPointF extrude(const QRectF &);
virtual bool contains(const QRectF &bounds, const QPointF &point);
};
QT_END_NAMESPACE
#endif // PARTICLEEXTRUDER_H
@@ -0,0 +1,115 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
#include "qquickparticlegroup_p.h"
/*!
\qmltype ParticleGroup
\inqmlmodule QtQuick.Particles
\brief For setting attributes on a logical particle group.
\ingroup qtquick-particles
This element allows you to set timed transitions on particle groups.
You can also use this element to group particle system elements related to the logical
particle group. Emitters, Affectors and Painters set as direct children of a ParticleGroup
will automatically apply to that logical particle group. TrailEmitters will automatically follow
the group.
If a ParticleGroup element is not defined for a group, the group will function normally as if
none of the transition properties were set.
*/
/*!
\qmlproperty ParticleSystem QtQuick.Particles::ParticleGroup::system
This is the system which will contain the group.
If the ParticleGroup is a direct child of a ParticleSystem, it will automatically be associated with it.
*/
/*!
\qmlproperty string QtQuick.Particles::ParticleGroup::name
This is the name of the particle group, and how it is generally referred to by other elements.
If elements refer to a name which does not have an explicit ParticleGroup created, it will
work normally (with no transitions specified for the group). If you do not need to assign
duration based transitions to a group, you do not need to create a ParticleGroup with that name (although you may).
*/
/*!
\qmlproperty int QtQuick.Particles::ParticleGroup::duration
The time in milliseconds before the group will attempt to transition.
*/
/*!
\qmlproperty ParticleSystem QtQuick.Particles::ParticleGroup::durationVariation
The maximum number of milliseconds that the duration of the transition cycle varies per particle in the group.
Default value is zero.
*/
/*!
\qmlproperty ParticleSystem QtQuick.Particles::ParticleGroup::to
The weighted list of transitions valid for this group.
If the chosen transition stays in this group, another duration (+/- up to durationVariation)
milliseconds will occur before another transition is attempted.
*/
QQuickParticleGroup::QQuickParticleGroup(QObject* parent)
: QQuickStochasticState(parent)
, m_system(nullptr)
{
}
void delayedRedirect(QQmlListProperty<QObject> *prop, QObject *value)
{
QQuickParticleGroup* pg = qobject_cast<QQuickParticleGroup*>(prop->object);
if (pg)
pg->delayRedirect(value);
}
QQmlListProperty<QObject> QQuickParticleGroup::particleChildren()
{
QQuickParticleSystem* system = qobject_cast<QQuickParticleSystem*>(parent());
if (system) {
return QQmlListProperty<QObject>(this, nullptr,
&QQuickParticleSystem::statePropertyRedirect, nullptr,
nullptr, nullptr, nullptr, nullptr);
} else {
return QQmlListProperty<QObject>(this, nullptr,
&delayedRedirect, nullptr, nullptr,
nullptr, nullptr, nullptr);
}
}
void QQuickParticleGroup::setSystem(QQuickParticleSystem* arg)
{
if (m_system != arg) {
m_system = arg;
m_system->registerParticleGroup(this);
performDelayedRedirects();
emit systemChanged(arg);
}
}
void QQuickParticleGroup::delayRedirect(QObject *obj)
{
m_delayedRedirects << obj;
}
void QQuickParticleGroup::performDelayedRedirects()
{
if (!m_system)
return;
foreach (QObject* obj, m_delayedRedirects)
m_system->stateRedirect(this, m_system, obj);
m_delayedRedirects.clear();
}
void QQuickParticleGroup::componentComplete(){
if (!m_system && qobject_cast<QQuickParticleSystem*>(parent()))
setSystem(qobject_cast<QQuickParticleSystem*>(parent()));
}
#include "moc_qquickparticlegroup_p.cpp"
@@ -0,0 +1,69 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QQuickPARTICLEGROUP
#define QQuickPARTICLEGROUP
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <private/qquickspriteengine_p.h>
#include "qquickparticlesystem_p.h"
#include "qqmlparserstatus.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickParticleGroup : public QQuickStochasticState,
public QQmlParserStatus
{
Q_OBJECT
Q_PROPERTY(QQuickParticleSystem* system READ system WRITE setSystem NOTIFY systemChanged)
//Intercept children requests and assign to the group & system
Q_PROPERTY(QQmlListProperty<QObject> particleChildren READ particleChildren DESIGNABLE false)//### Hidden property for in-state system definitions - ought not to be used in actual "Sprite" states
Q_CLASSINFO("DefaultProperty", "particleChildren")
QML_NAMED_ELEMENT(ParticleGroup)
QML_ADDED_IN_VERSION(2, 0)
Q_INTERFACES(QQmlParserStatus)
public:
explicit QQuickParticleGroup(QObject *parent = nullptr);
QQmlListProperty<QObject> particleChildren();
QQuickParticleSystem* system() const
{
return m_system;
}
public Q_SLOTS:
void setSystem(QQuickParticleSystem* arg);
void delayRedirect(QObject* obj);
Q_SIGNALS:
void systemChanged(QQuickParticleSystem* arg);
protected:
void componentComplete() override;
void classBegin() override {}
private:
void performDelayedRedirects();
QQuickParticleSystem* m_system;
QList<QObject*> m_delayedRedirects;
};
QT_END_NAMESPACE
#endif
@@ -0,0 +1,170 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
#include "qquickparticlepainter_p.h"
#include <QQuickWindow>
#include <QDebug>
QT_BEGIN_NAMESPACE
/*!
\qmltype ParticlePainter
\nativetype QQuickParticlePainter
\inqmlmodule QtQuick.Particles
\inherits Item
\brief For specifying how to paint particles.
\ingroup qtquick-particles
The default implementation paints nothing. See the subclasses if you want to
paint something visible.
*/
/*!
\qmlproperty ParticleSystem QtQuick.Particles::ParticlePainter::system
This is the system whose particles can be painted by the element.
If the ParticlePainter is a direct child of a ParticleSystem, it will automatically be associated with it.
*/
/*!
\qmlproperty list<string> QtQuick.Particles::ParticlePainter::groups
Which logical particle groups will be painted.
If empty, it will paint the default particle group ("").
*/
QQuickParticlePainter::QQuickParticlePainter(QQuickItem *parent)
: QQuickItem(parent)
, m_system(nullptr)
, m_count(0)
, m_pleaseReset(true)
, m_window(nullptr)
, m_windowChanged(false)
, m_groupIdsNeedRecalculation(false)
{
}
void QQuickParticlePainter::itemChange(ItemChange change, const ItemChangeData &data)
{
if (change == QQuickItem::ItemSceneChange) {
if (m_window) {
disconnect(m_window, &QQuickWindow::sceneGraphInvalidated,
this, &QQuickParticlePainter::sceneGraphInvalidated);
}
m_window = data.window;
m_windowChanged = true;
if (m_window) {
connect(m_window, &QQuickWindow::sceneGraphInvalidated,
this, &QQuickParticlePainter::sceneGraphInvalidated, Qt::DirectConnection);
}
}
QQuickItem::itemChange(change, data);
}
void QQuickParticlePainter::componentComplete()
{
if (!m_system && qobject_cast<QQuickParticleSystem*>(parentItem()))
setSystem(qobject_cast<QQuickParticleSystem*>(parentItem()));
QQuickItem::componentComplete();
}
void QQuickParticlePainter::recalculateGroupIds() const
{
if (!m_system) {
m_groupIds.clear();
return;
}
m_groupIdsNeedRecalculation = false;
m_groupIds.clear();
const auto groupList = groups();
for (const QString &str : groupList) {
QQuickParticleGroupData::ID groupId = m_system->groupIds.value(str, QQuickParticleGroupData::InvalidID);
if (groupId == QQuickParticleGroupData::InvalidID) {
// invalid data, not finished setting up, or whatever. Fallback: do not cache.
m_groupIdsNeedRecalculation = true;
} else {
m_groupIds.append(groupId);
}
}
}
void QQuickParticlePainter::setSystem(QQuickParticleSystem *arg)
{
if (m_system != arg) {
m_system = arg;
m_groupIdsNeedRecalculation = true;
if (m_system){
m_system->registerParticlePainter(this);
reset();
}
emit systemChanged(arg);
}
}
void QQuickParticlePainter::setGroups(const QStringList &arg)
{
if (m_groups != arg) {
m_groups = arg;
m_groupIdsNeedRecalculation = true;
//Note: The system watches this as it has to recalc things when groups change. It will request a reset if necessary
Q_EMIT groupsChanged(arg);
}
}
void QQuickParticlePainter::load(QQuickParticleData* d)
{
initialize(d->groupId, d->index);
if (m_pleaseReset)
return;
m_pendingCommits << std::make_pair(d->groupId, d->index);
}
void QQuickParticlePainter::reload(QQuickParticleData* d)
{
if (m_pleaseReset)
return;
m_pendingCommits << std::make_pair(d->groupId, d->index);
}
void QQuickParticlePainter::reset()
{
m_pendingCommits.clear();
m_pleaseReset = true;
}
void QQuickParticlePainter::setCount(int c)//### TODO: some resizeing so that particles can reallocate on size change instead of recreate
{
Q_ASSERT(c >= 0); //XXX
if (c == m_count)
return;
m_count = c;
emit countChanged();
reset();
}
void QQuickParticlePainter::calcSystemOffset(bool resetPending)
{
if (!m_system || !parentItem())
return;
QPointF lastOffset = m_systemOffset;
m_systemOffset = -1 * this->mapFromItem(m_system, QPointF(0.0, 0.0));
if (lastOffset != m_systemOffset && !resetPending){
//Reload all particles//TODO: Necessary?
foreach (const QString &g, m_groups){
int gId = m_system->groupIds[g];
foreach (QQuickParticleData* d, m_system->groupData[gId]->data)
reload(d);
}
}
}
typedef std::pair<int,int> intPair;
void QQuickParticlePainter::performPendingCommits()
{
calcSystemOffset();
foreach (intPair p, m_pendingCommits)
commit(p.first, p.second);
m_pendingCommits.clear();
}
QT_END_NAMESPACE
#include "moc_qquickparticlepainter_p.cpp"
@@ -0,0 +1,125 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef PARTICLE_H
#define PARTICLE_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QObject>
#include <QDebug>
#include <utility>
#include "qquickparticlesystem_p.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickParticlePainter : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QQuickParticleSystem* system READ system WRITE setSystem NOTIFY systemChanged)
Q_PROPERTY(QStringList groups READ groups WRITE setGroups NOTIFY groupsChanged)
QML_NAMED_ELEMENT(ParticlePainter)
QML_ADDED_IN_VERSION(2, 0)
QML_UNCREATABLE("Abstract type. Use one of the inheriting types instead.")
public: // data
typedef QQuickParticleVarLengthArray<QQuickParticleGroupData::ID, 4> GroupIDs;
public:
explicit QQuickParticlePainter(QQuickItem *parent = nullptr);
//Data Interface to system
void load(QQuickParticleData*);
void reload(QQuickParticleData*);
void setCount(int c);
int count() const
{
return m_count;
}
void performPendingCommits();//Called from updatePaintNode
QQuickParticleSystem* system() const
{
return m_system;
}
QStringList groups() const
{
return m_groups;
}
const GroupIDs &groupIds() const
{
if (m_groupIdsNeedRecalculation) {
recalculateGroupIds();
}
return m_groupIds;
}
void itemChange(ItemChange, const ItemChangeData &) override;
Q_SIGNALS:
void countChanged();
void systemChanged(QQuickParticleSystem* arg);
void groupsChanged(const QStringList &arg);
public Q_SLOTS:
void setSystem(QQuickParticleSystem* arg);
void setGroups(const QStringList &arg);
void calcSystemOffset(bool resetPending = false);
private Q_SLOTS:
virtual void sceneGraphInvalidated() {}
protected:
/* Reset resets all your internal data structures. But anything attached to a particle should
be in attached data. So reset + reloads should have no visible effect.
###Hunt down all cases where we do a complete reset for convenience and be more targeted
*/
virtual void reset();
void componentComplete() override;
virtual void initialize(int gIdx, int pIdx){//Called from main thread
Q_UNUSED(gIdx);
Q_UNUSED(pIdx);
}
virtual void commit(int gIdx, int pIdx){//Called in Render Thread
//###If you need to do something on size changed, check m_data size in this? Or we reset you every time?
Q_UNUSED(gIdx);
Q_UNUSED(pIdx);
}
QQuickParticleSystem* m_system;
friend class QQuickParticleSystem;
int m_count;
bool m_pleaseReset;//Used by subclasses, but it's a nice optimization to know when stuff isn't going to matter.
QPointF m_systemOffset;
QQuickWindow *m_window;
bool m_windowChanged;
private: // methods
void recalculateGroupIds() const;
private: // data
QStringList m_groups;
QSet<std::pair<int,int> > m_pendingCommits;
mutable GroupIDs m_groupIds;
mutable bool m_groupIdsNeedRecalculation;
};
QT_END_NAMESPACE
#endif // PARTICLE_H
@@ -0,0 +1,997 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickparticlesystem_p.h"
#include <QtQuick/qsgnode.h>
#include "qquickparticleemitter_p.h"
#include "qquickparticleaffector_p.h"
#include "qquickparticlepainter_p.h"
#include <private/qquickspriteengine_p.h>
#include <private/qquicksprite_p.h>
#include "qquickv4particledata_p.h"
#include "qquickparticlegroup_p.h"
#include "qquicktrailemitter_p.h"//###For auto-follow on states, perhaps should be in emitter?
#include <private/qqmlengine_p.h>
#include <private/qqmlglobal_p.h>
#include <private/qqmlvaluetypewrapper_p.h>
#include <cmath>
#include <QDebug>
QT_BEGIN_NAMESPACE
//###Switch to define later, for now user-friendly (no compilation) debugging is worth it
DEFINE_BOOL_CONFIG_OPTION(qmlParticlesDebug, QML_PARTICLES_DEBUG)
/* \internal ParticleSystem internals documentation
Affectors, Painters, Emitters and Groups all register themselves on construction as a callback
from their setSystem (or componentComplete if they have a system from a parent).
Particle data is stored by group, They have a group index (used by the particle system almost
everywhere) and a global index (used by the Stochastic state engine powering stochastic group
transitions). Each group has a recycling list/heap that stores the particle data.
The recycling list/heap is a heap of particle data sorted by when they're expected to die. If
they die prematurely then they are marked as reusable (and will probably still be alive when
they exit the heap). If they have their life extended, then they aren't dead when expected.
If this happens, they go back in the heap with the new estimate. If they have died on schedule,
then the indexes are marked as reusable. If no indexes are reusable when new particles are
requested, then the list is extended. This relatively complex datastructure is because memory
allocation and deallocation on this scale proved to be a significant performance cost. In order
to reuse the indexes validly (even when particles can have their life extended or cut short
dynamically, or particle counts grow) this seemed to be the most efficient option for keeping
track of which indices could be reused.
When a new particle is emitted, the emitter gets a new datum from the group (through the
system), and sets properties on it. Then it's passed back to the group briefly so that it can
now guess when the particle will die. Then the painters get a change to initialize properties
as well, since particle data includes shared data from painters as well as logical particle
data.
Every animation advance, the simulation advances by running all emitters for the elapsed
duration, then running all affectors, then telling all particle painters to update changed
particles. The ParticlePainter superclass stores these changes, and they are implemented
when the painter is called to paint in the render thread.
Particle group changes move the particle from one group to another by killing the old particle
and then creating a new one with the same data in the new group.
Note that currently groups only grow. Given that data is stored in vectors, it is non-trivial
to pluck out the unused indexes when the count goes down. Given the dynamic nature of the
system, it is difficult to tell if those unused data instances will be used again. Still,
some form of garbage collection is on the long term plan.
*/
/*!
\qmltype ParticleSystem
//! \nativetype QQuickParticleSystem
\inqmlmodule QtQuick.Particles
\brief A system which includes particle painter, emitter, and affector types.
\ingroup qtquick-particles
*/
/*!
\qmlproperty bool QtQuick.Particles::ParticleSystem::running
If running is set to false, the particle system will stop the simulation. All particles
will be destroyed when the system is set to running again.
It can also be controlled with the start() and stop() methods.
*/
/*!
\qmlproperty bool QtQuick.Particles::ParticleSystem::paused
If paused is set to true, the particle system will not advance the simulation. When
paused is set to false again, the simulation will resume from the same point it was
paused.
The simulation will automatically pause if it detects that there are no live particles
left, and unpause when new live particles are added.
It can also be controlled with the pause() and resume() methods.
*/
/*!
\qmlproperty bool QtQuick.Particles::ParticleSystem::empty
empty is set to true when there are no live particles left in the system.
You can use this to pause the system, keeping it from spending any time updating,
but you will need to resume it in order for additional particles to be generated
by the system.
To kill all the particles in the system, use an Age affector.
*/
/*!
\qmlproperty list<Sprite> QtQuick.Particles::ParticleSystem::particleStates
You can define a sub-set of particle groups in this property in order to provide them
with stochastic state transitions.
Each QtQuick::Sprite in this list is interpreted as corresponding to the particle group
with the same name. Any transitions defined in these sprites will take effect on the particle
groups as well. Additionally TrailEmitters, Affectors and ParticlePainters defined
inside one of these sprites are automatically associated with the corresponding particle group.
*/
/*!
\qmlmethod void QtQuick.Particles::ParticleSystem::pause()
Pauses the simulation if it is running.
\sa resume, paused
*/
/*!
\qmlmethod void QtQuick.Particles::ParticleSystem::resume()
Resumes the simulation if it is paused.
\sa pause, paused
*/
/*!
\qmlmethod void QtQuick.Particles::ParticleSystem::start()
Starts the simulation if it has not already running.
\sa stop, restart, running
*/
/*!
\qmlmethod void QtQuick.Particles::ParticleSystem::stop()
Stops the simulation if it is running.
\sa start, restart, running
*/
/*!
\qmlmethod void QtQuick.Particles::ParticleSystem::restart()
Stops the simulation if it is running, and then starts it.
\sa start, stop, running
*/
/*!
\qmlmethod void QtQuick.Particles::ParticleSystem::reset()
Discards all currently existing particles.
*/
static inline int roundedTime(qreal a)
{// in ms
return (int)qRound(a*1000.0);
}
QQuickParticleDataHeap::QQuickParticleDataHeap()
: m_data(0)
{
m_data.reserve(1000);
clear();
}
void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
{
m_data.resize(qsizetype(1) << ++m_size);
}
void QQuickParticleDataHeap::insert(QQuickParticleData* data)
{
insertTimed(data, roundedTime(data->t + data->lifeSpan));
}
void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time)
{
//TODO: Optimize 0 lifespan (or already dead) case
if (m_lookups.contains(time)) {
m_data[m_lookups[time]].data << data;
return;
}
if (m_end == (1 << m_size))
grow();
m_data[m_end].time = time;
m_data[m_end].data.clear();
m_data[m_end].data.insert(data);
m_lookups.insert(time, m_end);
bubbleUp(m_end++);
}
int QQuickParticleDataHeap::top()
{
Q_ASSERT(!isEmpty());
return m_data[0].time;
}
QSet<QQuickParticleData*> QQuickParticleDataHeap::pop()
{
if (!m_end)
return QSet<QQuickParticleData*> ();
QSet<QQuickParticleData*> ret = m_data[0].data;
m_lookups.remove(m_data[0].time);
if (m_end == 1) {
--m_end;
} else {
m_data[0] = m_data[--m_end];
bubbleDown(0);
}
return ret;
}
void QQuickParticleDataHeap::clear()
{
m_size = 0;
m_end = 0;
//m_size is in powers of two. So to start at 0 we have one allocated
m_data.resize(1);
m_lookups.clear();
}
bool QQuickParticleDataHeap::contains(QQuickParticleData* d)
{
for (int i=0; i<m_end; i++)
if (m_data[i].data.contains(d))
return true;
return false;
}
void QQuickParticleDataHeap::swap(int a, int b)
{
m_tmp = m_data[a];
m_data[a] = m_data[b];
m_data[b] = m_tmp;
m_lookups[m_data[a].time] = a;
m_lookups[m_data[b].time] = b;
}
void QQuickParticleDataHeap::bubbleUp(int idx)//tends to be called once
{
if (!idx)
return;
int parent = (idx-1)/2;
if (m_data[idx].time < m_data[parent].time) {
swap(idx, parent);
bubbleUp(parent);
}
}
void QQuickParticleDataHeap::bubbleDown(int idx)//tends to be called log n times
{
int left = idx*2 + 1;
if (left >= m_end)
return;
int lesser = left;
int right = idx*2 + 2;
if (right < m_end) {
if (m_data[left].time > m_data[right].time)
lesser = right;
}
if (m_data[idx].time > m_data[lesser].time) {
swap(idx, lesser);
bubbleDown(lesser);
}
}
QQuickParticleGroupData::QQuickParticleGroupData(const QString &name, QQuickParticleSystem* sys)
: index(sys->registerParticleGroupData(name, this))
, m_size(0)
, m_system(sys)
{
initList();
}
QQuickParticleGroupData::~QQuickParticleGroupData()
{
for (QQuickParticleData *d : std::as_const(data))
delete d;
}
QString QQuickParticleGroupData::name() const//### Worth caching as well?
{
return m_system->groupIds.key(index);
}
void QQuickParticleGroupData::setSize(int newSize)
{
if (newSize == m_size)
return;
Q_ASSERT(newSize > m_size);//XXX allow shrinking
data.resize(newSize);
freeList.resize(newSize);
for (int i=m_size; i<newSize; i++) {
data[i] = new QQuickParticleData;
data[i]->groupId = index;
data[i]->index = i;
}
int delta = newSize - m_size;
m_size = newSize;
for (QQuickParticlePainter *p : std::as_const(painters))
p->setCount(p->count() + delta);
}
void QQuickParticleGroupData::initList()
{
dataHeap.clear();
}
void QQuickParticleGroupData::kill(QQuickParticleData* d)
{
Q_ASSERT(d->groupId == index);
d->lifeSpan = 0;//Kill off
for (QQuickParticlePainter *p : std::as_const(painters))
p->reload(d);
freeList.free(d->index);
}
QQuickParticleData* QQuickParticleGroupData::newDatum(bool respectsLimits)
{
//recycle();//Extra recycler round to be sure?
while (freeList.hasUnusedEntries()) {
int idx = freeList.alloc();
if (data[idx]->stillAlive(m_system)) {// ### This means resurrection of 'dead' particles. Is that allowed?
prepareRecycler(data[idx]);
continue;
}
return data[idx];
}
if (respectsLimits)
return nullptr;
int oldSize = m_size;
setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily
int idx = freeList.alloc();
Q_ASSERT(idx == oldSize);
return data[idx];
}
bool QQuickParticleGroupData::recycle()
{
m_latestAliveParticles.clear();
while (!dataHeap.isEmpty() && dataHeap.top() <= m_system->timeInt) {
for (QQuickParticleData *datum : dataHeap.pop()) {
if (!datum->stillAlive(m_system)) {
freeList.free(datum->index);
} else {
m_latestAliveParticles.push_back(datum);
}
}
}
for (auto particle : m_latestAliveParticles)
prepareRecycler(particle); //ttl has been altered mid-way, put it back
//TODO: If the data is clear, gc (consider shrinking stack size)?
return freeList.count() == 0;
}
void QQuickParticleGroupData::prepareRecycler(QQuickParticleData* d)
{
if (d->lifeSpan*1000 < m_system->maxLife) {
dataHeap.insert(d);
} else {
int extend = 2 * m_system->maxLife / 3;
while ((roundedTime(d->t) + extend) <= m_system->timeInt)
d->extendLife(m_system->maxLife / 3000.0, m_system);
dataHeap.insertTimed(d, roundedTime(d->t) + extend);
}
}
QQuickV4ParticleData QQuickParticleData::v4Value(QQuickParticleSystem *particleSystem)
{
return QQuickV4ParticleData(this, particleSystem);
}
void QQuickParticleData::debugDump(QQuickParticleSystem* particleSystem) const
{
qDebug() << "Particle" << systemIndex << groupId << "/" << index << stillAlive(particleSystem)
<< "Pos: " << x << "," << y
<< "Vel: " << vx << "," << vy
<< "Acc: " << ax << "," << ay
<< "Size: " << size << "," << endSize
<< "Time: " << t << "," <<lifeSpan << ";" << (particleSystem->timeInt / 1000.0) ;
}
void QQuickParticleData::extendLife(float time, QQuickParticleSystem* particleSystem)
{
qreal newX = curX(particleSystem);
qreal newY = curY(particleSystem);
qreal newVX = curVX(particleSystem);
qreal newVY = curVY(particleSystem);
t += time;
animT += time;
qreal elapsed = (particleSystem->timeInt / 1000.0) - t;
qreal evy = newVY - elapsed*ay;
qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
qreal evx = newVX - elapsed*ax;
qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
x = ex;
vx = evx;
y = ey;
vy = evy;
}
QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
QQuickItem(parent),
stateEngine(nullptr),
nextFreeGroupId(0),
m_animation(nullptr),
m_running(true),
initialized(0),
particleCount(0),
m_nextIndex(0),
m_componentComplete(false),
m_paused(false),
m_empty(true)
{
m_debugMode = qmlParticlesDebug();
}
QQuickParticleSystem::~QQuickParticleSystem()
{
for (QQuickParticleGroupData *gd : std::as_const(groupData))
delete gd;
}
void QQuickParticleSystem::initGroups()
{
m_reusableIndexes.clear();
m_nextIndex = 0;
qDeleteAll(groupData);
groupData.clear();
groupIds.clear();
nextFreeGroupId = 0;
for (auto e : std::as_const(m_emitters)) {
e->reclaculateGroupId();
}
for (QQuickParticlePainter *p : std::as_const(m_painters)) {
p->recalculateGroupIds();
}
QQuickParticleGroupData *pd = new QQuickParticleGroupData(QString(), this); // Default group
Q_ASSERT(pd->index == 0);
Q_UNUSED(pd);
}
void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
{
if (m_debugMode)
qDebug() << "Registering Painter" << p << "to" << this;
//TODO: a way to Unregister emitters, painters and affectors
m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
connect(p, &QQuickParticlePainter::groupsChanged, this, [this, p] { this->loadPainter(p); }, Qt::QueuedConnection);
loadPainter(p);
}
void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
{
if (m_debugMode)
qDebug() << "Registering Emitter" << e << "to" << this;
m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
}
void QQuickParticleSystem::finishRegisteringParticleEmitter(QQuickParticleEmitter* e)
{
connect(e, &QQuickParticleEmitter::particleCountChanged,
this, &QQuickParticleSystem::emittersChanged);
connect(e, &QQuickParticleEmitter::groupChanged,
this, &QQuickParticleSystem::emittersChanged);
if (m_componentComplete)
emitterAdded(e);
e->reset();//Start, so that starttime factors appropriately
}
void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
{
if (m_debugMode)
qDebug() << "Registering Affector" << a << "to" << this;
if (!m_affectors.contains(a))
m_affectors << QPointer<QQuickParticleAffector>(a);
}
void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
{
if (m_debugMode)
qDebug() << "Registering Group" << g << "to" << this;
m_groups << QPointer<QQuickParticleGroup>(g);
createEngine();
}
void QQuickParticleSystem::setRunning(bool arg)
{
if (m_running != arg) {
m_running = arg;
emit runningChanged(arg);
setPaused(false);
if (m_animation)//Not created until componentCompleted
m_running ? m_animation->start() : m_animation->stop();
reset();
}
}
void QQuickParticleSystem::setPaused(bool arg) {
if (m_paused != arg) {
m_paused = arg;
if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
m_paused ? m_animation->pause() : m_animation->resume();
if (!m_paused) {
for (QQuickParticlePainter *p : std::as_const(m_painters)) {
if (p) {
p->update();
}
}
}
emit pausedChanged(arg);
}
}
void QQuickParticleSystem::statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value)
{
//Hooks up automatic state-associated stuff
QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
if (!group || !sys || !value)
return;
stateRedirect(group, sys, value);
}
void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
{
QStringList list;
list << group->name();
QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
if (a) {
a->setParentItem(sys);
a->setGroups(list);
a->setSystem(sys);
return;
}
QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
if (fe) {
fe->setParentItem(sys);
fe->setFollow(group->name());
fe->setSystem(sys);
return;
}
QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
if (e) {
e->setParentItem(sys);
e->setGroup(group->name());
e->setSystem(sys);
return;
}
QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
if (p) {
p->setParentItem(sys);
p->setGroups(list);
p->setSystem(sys);
return;
}
qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
}
int QQuickParticleSystem::registerParticleGroupData(const QString &name, QQuickParticleGroupData *pgd)
{
Q_ASSERT(!groupIds.contains(name));
int id;
if (nextFreeGroupId >= groupData.size()) {
groupData.push_back(pgd);
nextFreeGroupId = groupData.size();
id = nextFreeGroupId - 1;
} else {
id = nextFreeGroupId;
groupData[id] = pgd;
searchNextFreeGroupId();
}
groupIds.insert(name, id);
return id;
}
void QQuickParticleSystem::searchNextFreeGroupId()
{
++nextFreeGroupId;
for (int ei = groupData.size(); nextFreeGroupId != ei; ++nextFreeGroupId) {
if (groupData[nextFreeGroupId] == nullptr) {
return;
}
}
}
void QQuickParticleSystem::componentComplete()
{
QQuickItem::componentComplete();
m_componentComplete = true;
m_animation = new QQuickParticleSystemAnimation(this);
reset();//restarts animation as well
}
void QQuickParticleSystem::reset()
{
if (!m_componentComplete)
return;
timeInt = 0;
//Clear guarded pointers which have been deleted
m_emitters.removeAll(nullptr);
m_painters.removeAll(nullptr);
m_affectors.removeAll(nullptr);
bySysIdx.resize(0);
initGroups();//Also clears all logical particles
if (!m_running)
return;
for (QQuickParticleEmitter *e : std::as_const(m_emitters))
e->reset();
emittersChanged();
for (QQuickParticlePainter *p : std::as_const(m_painters)) {
loadPainter(p);
p->reset();
}
//### Do affectors need reset too?
if (m_animation) {//Animation is explicitly disabled in benchmarks
//reset restarts animation (if running)
if ((m_animation->state() == QAbstractAnimation::Running))
m_animation->stop();
m_animation->start();
if (m_paused)
m_animation->pause();
}
initialized = true;
}
void QQuickParticleSystem::loadPainter(QQuickParticlePainter *painter)
{
if (!m_componentComplete || !painter)
return;
for (QQuickParticleGroupData *sg : groupData) {
sg->painters.removeOne(painter);
}
int particleCount = 0;
if (painter->groups().isEmpty()) {//Uses default particle
static QStringList def = QStringList() << QString();
painter->setGroups(def);
particleCount += groupData[0]->size();
groupData[0]->painters << painter;
} else {
for (auto groupId : painter->groupIds()) {
QQuickParticleGroupData *gd = groupData[groupId];
particleCount += gd->size();
gd->painters << painter;
}
}
painter->setCount(particleCount);
painter->update();//Initial update here
return;
}
void QQuickParticleSystem::emittersChanged()
{
if (!m_componentComplete)
return;
QList<int> previousSizes;
QList<int> newSizes;
previousSizes.reserve(groupData.size());
newSizes.reserve(groupData.size());
for (int i = 0, ei = groupData.size(); i != ei; ++i) {
previousSizes << groupData[i]->size();
newSizes << 0;
}
// Populate groups and set sizes.
for (int i = 0; i < m_emitters.size(); ) {
QQuickParticleEmitter *e = m_emitters.at(i);
if (!e) {
m_emitters.removeAt(i);
continue;
}
int groupId = e->groupId();
if (groupId == QQuickParticleGroupData::InvalidID) {
groupId = (new QQuickParticleGroupData(e->group(), this))->index;
previousSizes << 0;
newSizes << 0;
}
newSizes[groupId] += e->particleCount();
//###: Cull emptied groups?
++i;
}
//TODO: Garbage collection?
particleCount = 0;
for (int i = 0, ei = groupData.size(); i != ei; ++i) {
groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
particleCount += groupData[i]->size();
}
postProcessEmitters();
}
void QQuickParticleSystem::postProcessEmitters()
{
if (m_debugMode)
qDebug() << "Particle system emitters changed. New particle count: " << particleCount << "in" << groupData.size() << "groups.";
if (particleCount > bySysIdx.size())//New datum requests haven't updated it
bySysIdx.resize(particleCount);
for (QQuickParticleAffector *a : std::as_const(m_affectors)) {//Groups may have changed
if (a) {
a->m_updateIntSet = true;
}
}
for (QQuickParticlePainter *p : std::as_const(m_painters))
loadPainter(p);
if (!m_groups.isEmpty())
createEngine();
}
void QQuickParticleSystem::emitterAdded(QQuickParticleEmitter *e)
{
if (!m_componentComplete)
return;
// Populate group and set size.
const int groupId = e->groupId();
if (groupId == QQuickParticleGroupData::InvalidID) {
QQuickParticleGroupData *group = new QQuickParticleGroupData(e->group(), this);
group->setSize(e->particleCount());
} else {
QQuickParticleGroupData *group = groupData[groupId];
group->setSize(group->size() + e->particleCount());
}
// groupData can have changed independently, so we still have to iterate it all
// to count the particles.
particleCount = 0;
for (int i = 0, ei = groupData.size(); i != ei; ++i)
particleCount += groupData[i]->size();
postProcessEmitters();
}
void QQuickParticleSystem::createEngine()
{
if (!m_componentComplete)
return;
if (stateEngine && m_debugMode)
qDebug() << "Resetting Existing Sprite Engine...";
//### Solve the losses if size/states go down
for (QQuickParticleGroup *group : std::as_const(m_groups)) {
bool exists = false;
for (auto it = groupIds.keyBegin(), end = groupIds.keyEnd(); it != end; ++it) {
if (group->name() == *it) {
exists = true;
break;
}
}
if (!exists) {
new QQuickParticleGroupData(group->name(), this);
}
}
if (m_groups.size()) {
//Reorder groups List so as to have the same order as groupData
// TODO: can't we just merge the two lists?
QList<QQuickParticleGroup*> newList;
for (int i = 0, ei = groupData.size(); i != ei; ++i) {
bool exists = false;
QString name = groupData[i]->name();
for (QQuickParticleGroup *existing : std::as_const(m_groups)) {
if (existing->name() == name) {
newList << existing;
exists = true;
}
}
if (!exists) {
newList << new QQuickParticleGroup(this);
newList.back()->setName(name);
}
}
m_groups = newList;
QList<QQuickStochasticState*> states;
states.reserve(m_groups.size());
for (QQuickParticleGroup *g : std::as_const(m_groups))
states << (QQuickStochasticState*)g;
if (!stateEngine)
stateEngine = new QQuickStochasticEngine(this);
stateEngine->setCount(particleCount);
stateEngine->m_states = states;
connect(stateEngine, &QQuickStochasticEngine::stateChanged,
this, &QQuickParticleSystem::particleStateChange);
} else {
if (stateEngine)
delete stateEngine;
stateEngine = nullptr;
}
}
void QQuickParticleSystem::particleStateChange(int idx)
{
moveGroups(bySysIdx[idx], stateEngine->curState(idx));
}
void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
{
if (!d || newGIdx == d->groupId)
return;
QQuickParticleData *pd = newDatum(newGIdx, false, d->systemIndex, d);
if (!pd)
return;
finishNewDatum(pd);
d->systemIndex = -1;
groupData[d->groupId]->kill(d);
}
int QQuickParticleSystem::nextSystemIndex()
{
if (!m_reusableIndexes.isEmpty()) {
int ret = *(m_reusableIndexes.begin());
m_reusableIndexes.remove(ret);
return ret;
}
if (m_nextIndex >= bySysIdx.size()) {
bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
if (stateEngine)
stateEngine->setCount(bySysIdx.size());
}
return m_nextIndex++;
}
QQuickParticleData *QQuickParticleSystem::newDatum(
int groupId, bool respectLimits, int sysIndex,
const QQuickParticleData *cloneFrom)
{
Q_ASSERT(groupId < groupData.size());//XXX shouldn't really be an assert
QQuickParticleData *ret = groupData[groupId]->newDatum(respectLimits);
if (!ret)
return nullptr;
if (cloneFrom) {
// We need to retain the "identity" information of the new particle data since it may be
// "recycled" and still be tracked.
const int retainedIndex = ret->index;
const int retainedGroupId = ret->groupId;
const int retainedSystemIndex = ret->systemIndex;
*ret = *cloneFrom;
ret->index = retainedIndex;
ret->groupId = retainedGroupId;
ret->systemIndex = retainedSystemIndex;
}
if (sysIndex == -1) {
if (ret->systemIndex == -1)
ret->systemIndex = nextSystemIndex();
} else {
if (ret->systemIndex != -1) {
if (stateEngine)
stateEngine->stop(ret->systemIndex);
m_reusableIndexes << ret->systemIndex;
bySysIdx[ret->systemIndex] = 0;
}
ret->systemIndex = sysIndex;
}
bySysIdx[ret->systemIndex] = ret;
if (stateEngine)
stateEngine->start(ret->systemIndex, ret->groupId);
m_empty = false;
return ret;
}
void QQuickParticleSystem::emitParticle(QQuickParticleData* pd, QQuickParticleEmitter* particleEmitter)
{// called from prepareNextFrame()->emitWindow - enforce?
//Account for relative emitter position
bool okay = false;
QTransform t = particleEmitter->itemTransform(this, &okay);
if (okay) {
qreal tx,ty;
t.map(pd->x, pd->y, &tx, &ty);
pd->x = tx;
pd->y = ty;
}
finishNewDatum(pd);
}
void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
{
Q_ASSERT(pd);
groupData[pd->groupId]->prepareRecycler(pd);
for (QQuickParticleAffector *a : std::as_const(m_affectors))
if (a && a->m_needsReset)
a->reset(pd);
for (QQuickParticlePainter *p : std::as_const(groupData[pd->groupId]->painters))
if (p)
p->load(pd);
}
void QQuickParticleSystem::updateCurrentTime( int currentTime )
{
if (!initialized)
return;//error in initialization
//### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
qreal dt = timeInt / 1000.;
timeInt = currentTime;
qreal time = timeInt / 1000.;
dt = time - dt;
needsReset.clear();
m_emitters.removeAll(nullptr);
m_painters.removeAll(nullptr);
m_affectors.removeAll(nullptr);
bool oldClear = m_empty;
m_empty = true;
for (QQuickParticleGroupData *gd : std::as_const(groupData))//Recycle all groups and see if they're out of live particles
m_empty = gd->recycle() && m_empty;
if (stateEngine)
stateEngine->updateSprites(timeInt);
for (QQuickParticleEmitter *emitter : std::as_const(m_emitters))
emitter->emitWindow(timeInt);
for (QQuickParticleAffector *a : std::as_const(m_affectors))
a->affectSystem(dt);
for (QQuickParticleData *d : needsReset)
for (QQuickParticlePainter *p : std::as_const(groupData[d->groupId]->painters))
p->reload(d);
if (oldClear != m_empty)
emptyChanged(m_empty);
}
int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
{
if (!m_running)
return 0;
if (!initialized)
return 0;//error in initialization
p->performPendingCommits();
return timeInt;
}
QT_END_NAMESPACE
#include "moc_qquickparticlesystem_p.cpp"
@@ -0,0 +1,583 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef PARTICLESYSTEM_H
#define PARTICLESYSTEM_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtQuick/QQuickItem>
#include <QElapsedTimer>
#include <QList>
#include <QVarLengthArray>
#include <QHash>
#include <QSet>
#include <QPointer>
#include <private/qquicksprite_p.h>
#include <QAbstractAnimation>
#include <QtQml/qqml.h>
#include <private/qv4util_p.h>
#include <private/qv4global_p.h>
#include <private/qv4staticvalue_p.h>
#include <private/qtquickparticlesglobal_p.h>
QT_BEGIN_NAMESPACE
template<class T, int Prealloc>
class QQuickParticleVarLengthArray: public QVarLengthArray<T, Prealloc>
{
public:
void insert(const T &element)
{
if (!this->contains(element)) {
this->append(element);
}
}
bool removeOne(const T &element)
{
for (int i = 0; i < this->size(); ++i) {
if (this->at(i) == element) {
this->remove(i);
return true;
}
}
return false;
}
};
class QQuickParticleSystem;
class QQuickParticleAffector;
class QQuickParticleEmitter;
class QQuickParticlePainter;
class QQuickParticleData;
class QQuickParticleSystemAnimation;
class QQuickStochasticEngine;
class QQuickSprite;
class QQuickV4ParticleData;
class QQuickParticleGroup;
class QQuickImageParticle;
struct QQuickParticleDataHeapNode{
int time;//in ms
QSet<QQuickParticleData*> data;//Set ptrs instead?
};
class Q_QUICKPARTICLES_EXPORT QQuickParticleDataHeap {
//Idea is to do a binary heap, but which also stores a set of int,Node* so that if the int already exists, you can
//add it to the data* list. Pops return the whole list at once.
public:
QQuickParticleDataHeap();
void insert(QQuickParticleData* data);
void insertTimed(QQuickParticleData* data, int time);
int top();
bool isEmpty() const { return m_end == 0; }
QSet<QQuickParticleData*> pop();
void clear();
bool contains(QQuickParticleData*);//O(n), for debugging purposes only
private:
void grow();
void swap(int, int);
void bubbleUp(int);
void bubbleDown(int);
int m_size;
int m_end;
QQuickParticleDataHeapNode m_tmp;
QList<QQuickParticleDataHeapNode> m_data;
QHash<int,int> m_lookups;
};
class Q_QUICKPARTICLES_EXPORT QQuickParticleGroupData {
class FreeList
{
public:
FreeList() {}
void resize(int newSize)
{
Q_ASSERT(newSize >= 0);
int oldSize = isUnused.size();
isUnused.resize(newSize, true);
if (newSize > oldSize) {
if (firstUnused == UINT_MAX) {
firstUnused = oldSize;
} else {
firstUnused = std::min(firstUnused, unsigned(oldSize));
}
} else if (firstUnused >= unsigned(newSize)) {
firstUnused = UINT_MAX;
}
}
void free(int index)
{
isUnused.setBit(index);
firstUnused = std::min(firstUnused, unsigned(index));
--allocated;
}
int count() const
{ return allocated; }
bool hasUnusedEntries() const
{ return firstUnused != UINT_MAX; }
int alloc()
{
if (hasUnusedEntries()) {
int nextFree = firstUnused;
isUnused.clearBit(firstUnused);
firstUnused = isUnused.findNext(firstUnused, true, false);
if (firstUnused >= unsigned(isUnused.size())) {
firstUnused = UINT_MAX;
}
++allocated;
return nextFree;
} else {
return -1;
}
}
private:
QV4::BitVector isUnused;
unsigned firstUnused = UINT_MAX;
int allocated = 0;
};
public: // types
typedef int ID;
enum { InvalidID = -1, DefaultGroupID = 0 };
public:
QQuickParticleGroupData(const QString &name, QQuickParticleSystem* sys);
~QQuickParticleGroupData();
int size() const
{
return m_size;
}
bool isActive() { return freeList.count() > 0; }
QString name() const;
void setSize(int newSize);
const ID index;
QQuickParticleVarLengthArray<QQuickParticlePainter*, 4> painters;//TODO: What if they are dynamically removed?
//TODO: Refactor particle data list out into a separate class
QList<QQuickParticleData*> data;
FreeList freeList;
QQuickParticleDataHeap dataHeap;
bool recycle(); //Force recycling round, returns true if all indexes are now reusable
void initList();
void kill(QQuickParticleData* d);
//After calling this, initialize, then call prepareRecycler(d)
QQuickParticleData* newDatum(bool respectsLimits);
//TODO: Find and clean up those that don't get added to the recycler (currently they get lost)
void prepareRecycler(QQuickParticleData* d);
private:
int m_size;
QQuickParticleSystem* m_system;
// Only used in recycle() for tracking of alive particles after latest recycling round
QList<QQuickParticleData*> m_latestAliveParticles;
};
struct Color4ub {
uchar r;
uchar g;
uchar b;
uchar a;
};
class Q_QUICKPARTICLES_EXPORT QQuickParticleData
{
public:
//Convenience functions for working backwards, because parameters are from the start of particle life
//If setting multiple parameters at once, doing the conversion yourself will be faster.
//sets the x accleration without affecting the instantaneous x velocity or position
void setInstantaneousAX(float ax, QQuickParticleSystem *particleSystem);
//sets the x velocity without affecting the instantaneous x postion
void setInstantaneousVX(float vx, QQuickParticleSystem *particleSystem);
//sets the instantaneous x postion
void setInstantaneousX(float x, QQuickParticleSystem *particleSystem);
//sets the y accleration without affecting the instantaneous y velocity or position
void setInstantaneousAY(float ay, QQuickParticleSystem *particleSystem);
//sets the y velocity without affecting the instantaneous y postion
void setInstantaneousVY(float vy, QQuickParticleSystem *particleSystem);
//sets the instantaneous Y postion
void setInstantaneousY(float y, QQuickParticleSystem *particleSystem);
//TODO: Slight caching?
float curX(QQuickParticleSystem *particleSystem) const;
float curVX(QQuickParticleSystem *particleSystem) const;
float curAX() const { return ax; }
float curAX(QQuickParticleSystem *) const { return ax; } // used by the macros in qquickv4particledata.cpp
float curY(QQuickParticleSystem *particleSystem) const;
float curVY(QQuickParticleSystem *particleSystem) const;
float curAY() const { return ay; }
float curAY(QQuickParticleSystem *) const { return ay; } // used by the macros in qquickv4particledata.cpp
int index = 0;
int systemIndex = -1;
//General Position Stuff
float x = 0;
float y = 0;
float t = -1;
float lifeSpan = 0;
float size = 0;
float endSize = 0;
float vx = 0;
float vy = 0;
float ax = 0;
float ay = 0;
//Painter-specific stuff, now universally shared
//Used by ImageParticle color mode
Color4ub color = { 255, 255, 255, 255};
//Used by ImageParticle deform mode
float xx = 1;
float xy = 0;
float yx = 0;
float yy = 1;
float rotation = 0;
float rotationVelocity = 0;
uchar autoRotate = 0; // Basically a bool
//Used by ImageParticle Sprite mode
float animIdx = 0;
float frameDuration = 1;
float frameAt = -1;//Used for duration -1
float frameCount = 1;
float animT = -1;
float animX = 0;
float animY = 0;
float animWidth = 1;
float animHeight = 1;
QQuickParticleGroupData::ID groupId = 0;
//Used by ImageParticle data shadowing
QQuickImageParticle* colorOwner = nullptr;
QQuickImageParticle* rotationOwner = nullptr;
QQuickImageParticle* deformationOwner = nullptr;
QQuickImageParticle* animationOwner = nullptr;
//Used by ItemParticle
QQuickItem* delegate = nullptr;
//Used by custom affectors
float update = 0;
void debugDump(QQuickParticleSystem *particleSystem) const;
bool stillAlive(QQuickParticleSystem *particleSystem) const; //Only checks end, because usually that's all you need and it's a little faster.
bool alive(QQuickParticleSystem *particleSystem) const;
float lifeLeft(QQuickParticleSystem *particleSystem) const;
float curSize(QQuickParticleSystem *particleSystem) const;
QQuickV4ParticleData v4Value(QQuickParticleSystem *particleSystem);
void extendLife(float time, QQuickParticleSystem *particleSystem);
static inline constexpr float EPSILON() noexcept { return 0.001f; }
};
static_assert(std::is_trivially_copyable_v<QQuickParticleData>);
static_assert(std::is_trivially_destructible_v<QQuickParticleData>);
class Q_QUICKPARTICLES_EXPORT QQuickParticleSystem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged)
Q_PROPERTY(bool paused READ isPaused WRITE setPaused NOTIFY pausedChanged)
Q_PROPERTY(bool empty READ isEmpty NOTIFY emptyChanged)
QML_NAMED_ELEMENT(ParticleSystem)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickParticleSystem(QQuickItem *parent = nullptr);
~QQuickParticleSystem();
bool isRunning() const
{
return m_running;
}
int count() const
{
return particleCount;
}
static const int maxLife = 600000;
Q_SIGNALS:
void systemInitialized();
void runningChanged(bool arg);
void pausedChanged(bool arg);
void emptyChanged(bool arg);
public Q_SLOTS:
void start(){setRunning(true);}
void stop(){setRunning(false);}
void restart(){setRunning(false);setRunning(true);}
void pause(){setPaused(true);}
void resume(){setPaused(false);}
void reset();
void setRunning(bool arg);
void setPaused(bool arg);
virtual int duration() const { return -1; }
protected:
//This one only once per frame (effectively)
void componentComplete() override;
private Q_SLOTS:
void emittersChanged();
void loadPainter(QQuickParticlePainter *p);
void createEngine(); //Not invoked by sprite engine, unlike Sprite uses
void particleStateChange(int idx);
public:
//These can be called multiple times per frame, performance critical
void emitParticle(QQuickParticleData* p, QQuickParticleEmitter *particleEmitter);
QQuickParticleData *newDatum(
int groupId, bool respectLimits = true, int sysIdx = -1,
const QQuickParticleData *cloneFrom = nullptr);
void finishNewDatum(QQuickParticleData*);
void moveGroups(QQuickParticleData *d, int newGIdx);
int nextSystemIndex();
//This one only once per painter per frame
int systemSync(QQuickParticlePainter* p);
//Data members here for ease of related class and auto-test usage. Not "public" API. TODO: d_ptrize
QSet<QQuickParticleData*> needsReset;
QList<QQuickParticleData*> bySysIdx; //Another reference to the data (data owned by group), but by sysIdx
QQuickStochasticEngine* stateEngine;
QHash<QString, int> groupIds;
QVarLengthArray<QQuickParticleGroupData*, 32> groupData;
int nextFreeGroupId;
int registerParticleGroupData(const QString &name, QQuickParticleGroupData *pgd);
//Also only here for auto-test usage
void updateCurrentTime( int currentTime );
QQuickParticleSystemAnimation* m_animation;
bool m_running;
bool m_debugMode;
int timeInt;
bool initialized;
int particleCount;
void registerParticlePainter(QQuickParticlePainter* p);
void registerParticleEmitter(QQuickParticleEmitter* e);
void finishRegisteringParticleEmitter(QQuickParticleEmitter *e);
void registerParticleAffector(QQuickParticleAffector* a);
void registerParticleGroup(QQuickParticleGroup* g);
static void statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value);
static void stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value);
bool isPaused() const
{
return m_paused;
}
bool isEmpty() const
{
return m_empty;
}
private:
void searchNextFreeGroupId();
private:
void emitterAdded(QQuickParticleEmitter *e);
void postProcessEmitters();
void initializeSystem();
void initGroups();
QList<QPointer<QQuickParticleEmitter> > m_emitters;
QList<QPointer<QQuickParticleAffector> > m_affectors;
QList<QPointer<QQuickParticlePainter> > m_painters;
QList<QPointer<QQuickParticlePainter> > m_syncList;
QList<QQuickParticleGroup*> m_groups;
int m_nextIndex;
QSet<int> m_reusableIndexes;
bool m_componentComplete;
bool m_paused;
bool m_allDead;
bool m_empty;
};
// Internally, this animation drives all the timing. Painters sync up in their updatePaintNode
class QQuickParticleSystemAnimation : public QAbstractAnimation
{
Q_OBJECT
public:
QQuickParticleSystemAnimation(QQuickParticleSystem* system)
: QAbstractAnimation(static_cast<QObject*>(system)), m_system(system)
{ }
protected:
void updateCurrentTime(int t) override
{
m_system->updateCurrentTime(t);
}
int duration() const override
{
return -1;
}
private:
QQuickParticleSystem* m_system;
};
inline void QQuickParticleData::setInstantaneousAX(float ax, QQuickParticleSystem* particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
float vx = (this->vx + t * this->ax) - t * ax;
float ex = this->x + this->vx * t + 0.5f * this->ax * t_sq;
float x = ex - t * vx - 0.5f * t_sq * ax;
this->ax = ax;
this->vx = vx;
this->x = x;
}
inline void QQuickParticleData::setInstantaneousVX(float vx, QQuickParticleSystem* particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
float evx = vx - t * this->ax;
float ex = this->x + this->vx * t + 0.5f * this->ax * t_sq;
float x = ex - t * evx - 0.5f * t_sq * this->ax;
this->vx = evx;
this->x = x;
}
inline void QQuickParticleData::setInstantaneousX(float x, QQuickParticleSystem* particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
this->x = x - t * this->vx - 0.5f * t_sq * this->ax;
}
inline void QQuickParticleData::setInstantaneousAY(float ay, QQuickParticleSystem* particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
float vy = (this->vy + t * this->ay) - t * ay;
float ey = this->y + this->vy * t + 0.5f * this->ay * t_sq;
float y = ey - t * vy - 0.5f * t_sq * ay;
this->ay = ay;
this->vy = vy;
this->y = y;
}
inline void QQuickParticleData::setInstantaneousVY(float vy, QQuickParticleSystem* particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
float evy = vy - t * this->ay;
float ey = this->y + this->vy * t + 0.5f * this->ay * t_sq;
float y = ey - t*evy - 0.5f * t_sq * this->ay;
this->vy = evy;
this->y = y;
}
inline void QQuickParticleData::setInstantaneousY(float y, QQuickParticleSystem *particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
this->y = y - t * this->vy - 0.5f * t_sq * this->ay;
}
inline float QQuickParticleData::curX(QQuickParticleSystem *particleSystem) const
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
return this->x + this->vx * t + 0.5f * this->ax * t_sq;
}
inline float QQuickParticleData::curVX(QQuickParticleSystem *particleSystem) const
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
return this->vx + t * this->ax;
}
inline float QQuickParticleData::curY(QQuickParticleSystem *particleSystem) const
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
return y + vy * t + 0.5f * ay * t_sq;
}
inline float QQuickParticleData::curVY(QQuickParticleSystem *particleSystem) const
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
return vy + t*ay;
}
inline bool QQuickParticleData::stillAlive(QQuickParticleSystem* system) const
{
if (!system)
return false;
return (t + lifeSpan - EPSILON()) > (system->timeInt / 1000.0f);
}
inline bool QQuickParticleData::alive(QQuickParticleSystem* system) const
{
if (!system)
return false;
float st = (system->timeInt / 1000.0f);
return (t + EPSILON()) < st && (t + lifeSpan - EPSILON()) > st;
}
inline float QQuickParticleData::lifeLeft(QQuickParticleSystem *particleSystem) const
{
if (!particleSystem)
return 0.0f;
return (t + lifeSpan) - (particleSystem->timeInt / 1000.0f);
}
inline float QQuickParticleData::curSize(QQuickParticleSystem *particleSystem) const
{
if (!particleSystem || lifeSpan == 0.0f)
return 0.0f;
return size + (endSize - size) * (1 - (lifeLeft(particleSystem) / lifeSpan));
}
QT_END_NAMESPACE
#endif // PARTICLESYSTEM_H
@@ -0,0 +1,124 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickpointattractor_p.h"
#include <cmath>
#include <QDebug>
QT_BEGIN_NAMESPACE
/*!
\qmltype Attractor
\nativetype QQuickAttractorAffector
\inqmlmodule QtQuick.Particles
\ingroup qtquick-particles
\inherits ParticleAffector
\brief Attracts particles towards a specific point.
Like other affectors, Attractor has the standard properties x, y, width,
and height that represent the affected area. The size and position of the
Attractor item determine the affected particles.
The size of the attracting point is always 0x0, and its location is
specified by \l pointX and \l pointY properties.
*/
/*!
\qmlproperty real QtQuick.Particles::Attractor::pointX
The x coordinate of the attracting point, relative
to the x coordinate of the Attractor item.
*/
/*!
\qmlproperty real QtQuick.Particles::Attractor::pointY
The y coordinate of the attracting point, relative
to the y coordinate of the Attractor item.
*/
/*!
\qmlproperty real QtQuick.Particles::Attractor::strength
The pull, in units per second, to be exerted on an item one pixel away.
Strength, together with the value of \l proportionalToDistance property,
determine the exact amount of pull exerted on particles at a distance.
*/
/*!
\qmlproperty enumeration QtQuick.Particles::Attractor::affectedParameter
The attribute of particles that is directly affected.
\value Attractor.Position Position
\value Attractor.Velocity Velocity
\value Attractor.Acceleration Acceleration
*/
/*!
\qmlproperty enumeration QtQuick.Particles::Attractor::proportionalToDistance
The relation between the \l strength of the attraction and the distance from
the particle to the attracting point.
\value Attractor.Constant Constant
\value Attractor.Linear Linear
\value Attractor.InverseLinear Inverse linear
\value Attractor.Quadratic Quadratic
\value Attractor.InverseQuadratic Inverse quadratic
*/
QQuickAttractorAffector::QQuickAttractorAffector(QQuickItem *parent) :
QQuickParticleAffector(parent), m_strength(0.0), m_x(0), m_y(0)
, m_physics(Velocity), m_proportionalToDistance(Linear)
{
}
bool QQuickAttractorAffector::affectParticle(QQuickParticleData *d, qreal dt)
{
if (m_strength == 0.0)
return false;
qreal dx = m_x+m_offset.x() - d->curX(m_system);
qreal dy = m_y+m_offset.y() - d->curY(m_system);
qreal r = std::sqrt((dx*dx) + (dy*dy));
qreal theta = std::atan2(dy,dx);
qreal ds = 0;
switch (m_proportionalToDistance){
case InverseQuadratic:
ds = (m_strength / qMax<qreal>(1.,r*r));
break;
case InverseLinear:
ds = (m_strength / qMax<qreal>(1.,r));
break;
case Quadratic:
ds = (m_strength * qMax<qreal>(1.,r*r));
break;
case Linear:
ds = (m_strength * qMax<qreal>(1.,r));
break;
default: //also Constant
ds = m_strength;
}
ds *= dt;
dx = ds * std::cos(theta);
dy = ds * std::sin(theta);
qreal vx,vy;
switch (m_physics){
case Position:
d->x = (d->x + dx);
d->y = (d->y + dy);
break;
case Acceleration:
d->setInstantaneousAX(d->ax + dx, m_system);
d->setInstantaneousAY(d->ay + dy, m_system);
break;
case Velocity: //also default
default:
vx = d->curVX(m_system);
vy = d->curVY(m_system);
d->setInstantaneousVX(vx + dx, m_system);
d->setInstantaneousVY(vy + dy, m_system);
}
return true;
}
QT_END_NAMESPACE
#include "moc_qquickpointattractor_p.cpp"
@@ -0,0 +1,140 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef ATTRACTORAFFECTOR_H
#define ATTRACTORAFFECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleaffector_p.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickAttractorAffector : public QQuickParticleAffector
{
Q_OBJECT
Q_PROPERTY(qreal strength READ strength WRITE setStrength NOTIFY strengthChanged)
Q_PROPERTY(qreal pointX READ pointX WRITE setPointX NOTIFY pointXChanged)
Q_PROPERTY(qreal pointY READ pointY WRITE setPointY NOTIFY pointYChanged)
Q_PROPERTY(AffectableParameters affectedParameter READ affectedParameter WRITE setAffectedParameter NOTIFY affectedParameterChanged)
Q_PROPERTY(Proportion proportionalToDistance READ proportionalToDistance WRITE setProportionalToDistance NOTIFY proportionalToDistanceChanged)
QML_NAMED_ELEMENT(Attractor)
QML_ADDED_IN_VERSION(2, 0)
public:
enum Proportion{
Constant,
Linear,
Quadratic,
InverseLinear,
InverseQuadratic
};
Q_ENUM(Proportion)
enum AffectableParameters {
Position,
Velocity,
Acceleration
};
Q_ENUM(AffectableParameters)
explicit QQuickAttractorAffector(QQuickItem *parent = nullptr);
qreal strength() const
{
return m_strength;
}
qreal pointX() const
{
return m_x;
}
qreal pointY() const
{
return m_y;
}
AffectableParameters affectedParameter() const
{
return m_physics;
}
Proportion proportionalToDistance() const
{
return m_proportionalToDistance;
}
Q_SIGNALS:
void strengthChanged(qreal arg);
void pointXChanged(qreal arg);
void pointYChanged(qreal arg);
void affectedParameterChanged(AffectableParameters arg);
void proportionalToDistanceChanged(Proportion arg);
public Q_SLOTS:
void setStrength(qreal arg)
{
if (m_strength != arg) {
m_strength = arg;
Q_EMIT strengthChanged(arg);
}
}
void setPointX(qreal arg)
{
if (m_x != arg) {
m_x = arg;
Q_EMIT pointXChanged(arg);
}
}
void setPointY(qreal arg)
{
if (m_y != arg) {
m_y = arg;
Q_EMIT pointYChanged(arg);
}
}
void setAffectedParameter(AffectableParameters arg)
{
if (m_physics != arg) {
m_physics = arg;
Q_EMIT affectedParameterChanged(arg);
}
}
void setProportionalToDistance(Proportion arg)
{
if (m_proportionalToDistance != arg) {
m_proportionalToDistance = arg;
Q_EMIT proportionalToDistanceChanged(arg);
}
}
protected:
bool affectParticle(QQuickParticleData *d, qreal dt) override;
private:
qreal m_strength;
qreal m_x;
qreal m_y;
AffectableParameters m_physics;
Proportion m_proportionalToDistance;
};
QT_END_NAMESPACE
#endif // ATTRACTORAFFECTOR_H
@@ -0,0 +1,52 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickpointdirection_p.h"
#include <QRandomGenerator>
QT_BEGIN_NAMESPACE
/*!
\qmltype PointDirection
\nativetype QQuickPointDirection
\inqmlmodule QtQuick.Particles
\ingroup qtquick-particles
\inherits Direction
\brief For specifying a direction that varies in x and y components.
The PointDirection element allows both the specification of a direction by x and y components,
as well as varying the parameters by x or y component.
*/
/*!
\qmlproperty real QtQuick.Particles::PointDirection::x
*/
/*!
\qmlproperty real QtQuick.Particles::PointDirection::y
*/
/*!
\qmlproperty real QtQuick.Particles::PointDirection::xVariation
*/
/*!
\qmlproperty real QtQuick.Particles::PointDirection::yVariation
*/
QQuickPointDirection::QQuickPointDirection(QObject *parent) :
QQuickDirection(parent)
, m_x(0)
, m_y(0)
, m_xVariation(0)
, m_yVariation(0)
{
}
QPointF QQuickPointDirection::sample(const QPointF &)
{
QPointF ret;
ret.setX(m_x - m_xVariation + QRandomGenerator::global()->generateDouble() * m_xVariation * 2);
ret.setY(m_y - m_yVariation + QRandomGenerator::global()->generateDouble() * m_yVariation * 2);
return ret;
}
QT_END_NAMESPACE
#include "moc_qquickpointdirection_p.cpp"
@@ -0,0 +1,105 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef POINTVECTOR_H
#define POINTVECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickdirection_p.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickPointDirection : public QQuickDirection
{
Q_OBJECT
Q_PROPERTY(qreal x READ x WRITE setX NOTIFY xChanged)
Q_PROPERTY(qreal y READ y WRITE setY NOTIFY yChanged)
Q_PROPERTY(qreal xVariation READ xVariation WRITE setXVariation NOTIFY xVariationChanged)
Q_PROPERTY(qreal yVariation READ yVariation WRITE setYVariation NOTIFY yVariationChanged)
QML_NAMED_ELEMENT(PointDirection)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickPointDirection(QObject *parent = nullptr);
QPointF sample(const QPointF &from) override;
qreal x() const
{
return m_x;
}
qreal y() const
{
return m_y;
}
qreal xVariation() const
{
return m_xVariation;
}
qreal yVariation() const
{
return m_yVariation;
}
Q_SIGNALS:
void xChanged(qreal arg);
void yChanged(qreal arg);
void xVariationChanged(qreal arg);
void yVariationChanged(qreal arg);
public Q_SLOTS:
void setX(qreal arg)
{
if (m_x != arg) {
m_x = arg;
Q_EMIT xChanged(arg);
}
}
void setY(qreal arg)
{
if (m_y != arg) {
m_y = arg;
Q_EMIT yChanged(arg);
}
}
void setXVariation(qreal arg)
{
if (m_xVariation != arg) {
m_xVariation = arg;
Q_EMIT xVariationChanged(arg);
}
}
void setYVariation(qreal arg)
{
if (m_yVariation != arg) {
m_yVariation = arg;
Q_EMIT yVariationChanged(arg);
}
}
private:
qreal m_x;
qreal m_y;
qreal m_xVariation;
qreal m_yVariation;
};
QT_END_NAMESPACE
#endif // POINTVECTOR_H
@@ -0,0 +1,53 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickrectangleextruder_p.h"
#include <QRandomGenerator>
QT_BEGIN_NAMESPACE
/*!
\qmltype RectangleShape
\nativetype QQuickRectangleExtruder
\inqmlmodule QtQuick.Particles
\brief For specifying an area for affectors and emitter.
\ingroup qtquick-particles
Just a rectangle.
*/
QQuickRectangleExtruder::QQuickRectangleExtruder(QObject *parent) :
QQuickParticleExtruder(parent), m_fill(true)
{
}
QPointF QQuickRectangleExtruder::extrude(const QRectF &rect)
{
if (m_fill)
return QPointF(QRandomGenerator::global()->generateDouble() * rect.width() + rect.x(),
QRandomGenerator::global()->generateDouble() * rect.height() + rect.y());
int side = QRandomGenerator::global()->bounded(4);
switch (side){//TODO: Doesn't this overlap the corners?
case 0:
return QPointF(rect.x(),
QRandomGenerator::global()->generateDouble() * rect.height() + rect.y());
case 1:
return QPointF(rect.width() + rect.x(),
QRandomGenerator::global()->generateDouble() * rect.height() + rect.y());
case 2:
return QPointF(QRandomGenerator::global()->generateDouble() * rect.width() + rect.x(),
rect.y());
default:
return QPointF(QRandomGenerator::global()->generateDouble() * rect.width() + rect.x(),
rect.height() + rect.y());
}
}
bool QQuickRectangleExtruder::contains(const QRectF &bounds, const QPointF &point)
{
return bounds.contains(point);
}
QT_END_NAMESPACE
#include "moc_qquickrectangleextruder_p.cpp"
@@ -0,0 +1,57 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef RECTANGLEEXTRUDER_H
#define RECTANGLEEXTRUDER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleextruder_p.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickRectangleExtruder : public QQuickParticleExtruder
{
Q_OBJECT
Q_PROPERTY(bool fill READ fill WRITE setFill NOTIFY fillChanged)
QML_NAMED_ELEMENT(RectangleShape)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickRectangleExtruder(QObject *parent = nullptr);
QPointF extrude(const QRectF &) override;
bool contains(const QRectF &bounds, const QPointF &point) override;
bool fill() const
{
return m_fill;
}
Q_SIGNALS:
void fillChanged(bool arg);
public Q_SLOTS:
void setFill(bool arg)
{
if (m_fill != arg) {
m_fill = arg;
Q_EMIT fillChanged(arg);
}
}
protected:
bool m_fill;
};
QT_END_NAMESPACE
#endif // RectangleEXTRUDER_H
@@ -0,0 +1,120 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickspritegoal_p.h"
#include <private/qquickspriteengine_p.h>
#include <private/qquicksprite_p.h>
#include "qquickimageparticle_p.h"
#include <QDebug>
QT_BEGIN_NAMESPACE
/*!
\qmltype SpriteGoal
\nativetype QQuickSpriteGoalAffector
\inqmlmodule QtQuick.Particles
\ingroup qtquick-images-sprites
\inherits ParticleAffector
\brief For changing the state of a sprite particle.
*/
/*!
\qmlproperty string QtQuick.Particles::SpriteGoal::goalState
The name of the Sprite which the affected particles should move to.
Sprite states have defined durations and transitions between them, setting goalState
will cause it to disregard any path weightings (including 0) and head down the path
which will reach the goalState quickest. It will pass through intermediate states
on that path.
*/
/*!
\qmlproperty bool QtQuick.Particles::SpriteGoal::jump
If true, affected sprites will jump directly to the goal state instead of taking the
shortest valid path to get there. They will also not finish their current state,
but immediately move to the beginning of the goal state.
Default is false.
*/
/*!
\qmlproperty bool QtQuick.Particles::SpriteGoal::systemStates
deprecated, use GroupGoal instead
*/
QQuickSpriteGoalAffector::QQuickSpriteGoalAffector(QQuickItem *parent) :
QQuickParticleAffector(parent),
m_goalIdx(-1),
m_lastEngine(nullptr),
m_jump(false),
m_systemStates(false),
m_notUsingEngine(false)
{
m_ignoresTime = true;
}
void QQuickSpriteGoalAffector::updateStateIndex(QQuickStochasticEngine* e)
{
if (m_systemStates){
m_goalIdx = m_system->groupIds[m_goalState];
}else{
m_lastEngine = e;
for (int i=0; i<e->stateCount(); i++){
if (e->state(i)->name() == m_goalState){
m_goalIdx = i;
return;
}
}
m_goalIdx = -1;//Can't find it
}
}
void QQuickSpriteGoalAffector::setGoalState(const QString &arg)
{
if (m_goalState != arg) {
m_goalState = arg;
emit goalStateChanged(arg);
if (m_goalState.isEmpty())
m_goalIdx = -1;
else
m_goalIdx = -2;
}
}
bool QQuickSpriteGoalAffector::affectParticle(QQuickParticleData *d, qreal dt)
{
Q_UNUSED(dt);
QQuickStochasticEngine *engine = nullptr;
if (!m_systemStates){
//TODO: Affect all engines
for (QQuickParticlePainter *p : m_system->groupData[d->groupId]->painters) {
if (qobject_cast<QQuickImageParticle*>(p))
engine = qobject_cast<QQuickImageParticle*>(p)->spriteEngine();
}
} else {
engine = m_system->stateEngine;
if (!engine)
m_notUsingEngine = true;
}
if (!engine && !m_notUsingEngine)
return false;
if (m_goalIdx == -2 || engine != m_lastEngine)
updateStateIndex(engine);
int index = d->index;
if (m_systemStates)
index = d->systemIndex;
if (m_notUsingEngine){//systemStates && no stochastic states defined. So cut out the engine
//TODO: It's possible to move to a group that is intermediate and not used by painters or emitters - but right now that will redirect to the default group
m_system->moveGroups(d, m_goalIdx);
}else if (engine->curState(index) != m_goalIdx){
engine->setGoal(m_goalIdx, index, m_jump);
return true; //Doesn't affect particle data, but necessary for onceOff
}
return false;
}
QT_END_NAMESPACE
#include "moc_qquickspritegoal_p.cpp"
@@ -0,0 +1,95 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef SPRITEGOALAFFECTOR_H
#define SPRITEGOALAFFECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleaffector_p.h"
#include <QtQml/qqmlinfo.h>
QT_BEGIN_NAMESPACE
class QQuickStochasticEngine;
class Q_QUICKPARTICLES_EXPORT QQuickSpriteGoalAffector : public QQuickParticleAffector
{
Q_OBJECT
Q_PROPERTY(QString goalState READ goalState WRITE setGoalState NOTIFY goalStateChanged)
Q_PROPERTY(bool jump READ jump WRITE setJump NOTIFY jumpChanged)
Q_PROPERTY(bool systemStates READ systemStates WRITE setSystemStates NOTIFY systemStatesChanged)
QML_NAMED_ELEMENT(SpriteGoal)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickSpriteGoalAffector(QQuickItem *parent = nullptr);
QString goalState() const
{
return m_goalState;
}
bool jump() const
{
return m_jump;
}
bool systemStates() const
{
return m_systemStates;
}
protected:
bool affectParticle(QQuickParticleData *d, qreal dt) override;
Q_SIGNALS:
void goalStateChanged(const QString &arg);
void jumpChanged(bool arg);
void systemStatesChanged(bool arg);
public Q_SLOTS:
void setGoalState(const QString &arg);
void setJump(bool arg)
{
if (m_jump != arg) {
m_jump = arg;
Q_EMIT jumpChanged(arg);
}
}
void setSystemStates(bool arg)
{
if (m_systemStates != arg) {
//TODO: GroupGoal was added (and this deprecated) Oct 4 - remove it in a few weeks.
qmlWarning(this) << "systemStates is deprecated and will be removed soon. Use GroupGoal instead.";
m_systemStates = arg;
Q_EMIT systemStatesChanged(arg);
}
}
private:
void updateStateIndex(QQuickStochasticEngine* e);
QString m_goalState;
int m_goalIdx;
QQuickStochasticEngine* m_lastEngine;
bool m_jump;
bool m_systemStates;
bool m_notUsingEngine;
};
QT_END_NAMESPACE
#endif // SPRITEGOALAFFECTOR_H
@@ -0,0 +1,98 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquicktargetdirection_p.h"
#include "qquickparticleemitter_p.h"
#include <cmath>
#include <QDebug>
#include <QRandomGenerator>
QT_BEGIN_NAMESPACE
/*!
\qmltype TargetDirection
\nativetype QQuickTargetDirection
\inqmlmodule QtQuick.Particles
\ingroup qtquick-particles
\inherits Direction
\brief For specifying a direction towards the target point.
*/
/*!
\qmlproperty real QtQuick.Particles::TargetDirection::targetX
*/
/*!
\qmlproperty real QtQuick.Particles::TargetDirection::targetY
*/
/*!
\qmlproperty Item QtQuick.Particles::TargetDirection::targetItem
If specified, this will take precedence over targetX and targetY.
The targeted point will be the center of the specified Item
*/
/*!
\qmlproperty real QtQuick.Particles::TargetDirection::targetVariation
*/
/*!
\qmlproperty real QtQuick.Particles::TargetDirection::magnitude
*/
/*!
\qmlproperty real QtQuick.Particles::TargetDirection::magnitudeVariation
*/
/*!
\qmlproperty bool QtQuick.Particles::TargetDirection::proportionalMagnitude
If true, then the value of magnitude and magnitudeVariation shall be interpreted as multiples
of the distance between the source point and the target point, per second.
If false(default), then the value of magnitude and magnitudeVariation shall be interpreted as
pixels per second.
*/
QQuickTargetDirection::QQuickTargetDirection(QObject *parent) :
QQuickDirection(parent)
, m_targetX(0)
, m_targetY(0)
, m_targetVariation(0)
, m_proportionalMagnitude(false)
, m_magnitude(0)
, m_magnitudeVariation(0)
, m_targetItem(nullptr)
{
}
QPointF QQuickTargetDirection::sample(const QPointF &from)
{
//###This approach loses interpolating the last position of the target (like we could with the emitter) is it worthwhile?
QPointF ret;
qreal targetX;
qreal targetY;
if (m_targetItem){
QQuickParticleEmitter* parentEmitter = qobject_cast<QQuickParticleEmitter*>(parent());
targetX = m_targetItem->width()/2;
targetY = m_targetItem->height()/2;
if (!parentEmitter){
qWarning() << "Directed vector is not a child of the emitter. Mapping of target item coordinates may fail.";
targetX += m_targetItem->x();
targetY += m_targetItem->y();
}else{
ret = parentEmitter->mapFromItem(m_targetItem, QPointF(targetX, targetY));
targetX = ret.x();
targetY = ret.y();
}
}else{
targetX = m_targetX;
targetY = m_targetY;
}
targetX += 0 - from.x() - m_targetVariation + QRandomGenerator::global()->generateDouble() * m_targetVariation*2;
targetY += 0 - from.y() - m_targetVariation + QRandomGenerator::global()->generateDouble() * m_targetVariation*2;
qreal theta = std::atan2(targetY, targetX);
qreal mag = m_magnitude + QRandomGenerator::global()->generateDouble() * m_magnitudeVariation * 2 - m_magnitudeVariation;
if (m_proportionalMagnitude)
mag *= qHypot(targetX, targetY);
ret.setX(mag * std::cos(theta));
ret.setY(mag * std::sin(theta));
return ret;
}
QT_END_NAMESPACE
#include "moc_qquicktargetdirection_p.cpp"
@@ -0,0 +1,163 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef DIRECTEDVECTOR_H
#define DIRECTEDVECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickdirection_p.h"
#include <QtQml/qqml.h>
QT_BEGIN_NAMESPACE
class QQuickItem;
class Q_QUICKPARTICLES_EXPORT QQuickTargetDirection : public QQuickDirection
{
Q_OBJECT
Q_PROPERTY(qreal targetX READ targetX WRITE setTargetX NOTIFY targetXChanged)
Q_PROPERTY(qreal targetY READ targetY WRITE setTargetY NOTIFY targetYChanged)
//If targetItem is set, X/Y are ignored. Aims at middle of item, use variation for variation
Q_PROPERTY(QQuickItem* targetItem READ targetItem WRITE setTargetItem NOTIFY targetItemChanged)
Q_PROPERTY(qreal targetVariation READ targetVariation WRITE setTargetVariation NOTIFY targetVariationChanged)
//TODO: An enum would be better
Q_PROPERTY(bool proportionalMagnitude READ proportionalMagnitude WRITE setProportionalMagnitude NOTIFY proprotionalMagnitudeChanged)
Q_PROPERTY(qreal magnitude READ magnitude WRITE setMagnitude NOTIFY magnitudeChanged)
Q_PROPERTY(qreal magnitudeVariation READ magnitudeVariation WRITE setMagnitudeVariation NOTIFY magnitudeVariationChanged)
QML_NAMED_ELEMENT(TargetDirection)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickTargetDirection(QObject *parent = nullptr);
QPointF sample(const QPointF &from) override;
qreal targetX() const
{
return m_targetX;
}
qreal targetY() const
{
return m_targetY;
}
qreal targetVariation() const
{
return m_targetVariation;
}
qreal magnitude() const
{
return m_magnitude;
}
bool proportionalMagnitude() const
{
return m_proportionalMagnitude;
}
qreal magnitudeVariation() const
{
return m_magnitudeVariation;
}
QQuickItem* targetItem() const
{
return m_targetItem;
}
Q_SIGNALS:
void targetXChanged(qreal arg);
void targetYChanged(qreal arg);
void targetVariationChanged(qreal arg);
void magnitudeChanged(qreal arg);
void proprotionalMagnitudeChanged(bool arg);
void magnitudeVariationChanged(qreal arg);
void targetItemChanged(QQuickItem* arg);
public Q_SLOTS:
void setTargetX(qreal arg)
{
if (m_targetX != arg) {
m_targetX = arg;
Q_EMIT targetXChanged(arg);
}
}
void setTargetY(qreal arg)
{
if (m_targetY != arg) {
m_targetY = arg;
Q_EMIT targetYChanged(arg);
}
}
void setTargetVariation(qreal arg)
{
if (m_targetVariation != arg) {
m_targetVariation = arg;
Q_EMIT targetVariationChanged(arg);
}
}
void setMagnitude(qreal arg)
{
if (m_magnitude != arg) {
m_magnitude = arg;
Q_EMIT magnitudeChanged(arg);
}
}
void setProportionalMagnitude(bool arg)
{
if (m_proportionalMagnitude != arg) {
m_proportionalMagnitude = arg;
Q_EMIT proprotionalMagnitudeChanged(arg);
}
}
void setMagnitudeVariation(qreal arg)
{
if (m_magnitudeVariation != arg) {
m_magnitudeVariation = arg;
Q_EMIT magnitudeVariationChanged(arg);
}
}
void setTargetItem(QQuickItem* arg)
{
if (m_targetItem != arg) {
m_targetItem = arg;
Q_EMIT targetItemChanged(arg);
}
}
private:
qreal m_targetX;
qreal m_targetY;
qreal m_targetVariation;
bool m_proportionalMagnitude;
qreal m_magnitude;
qreal m_magnitudeVariation;
QQuickItem *m_targetItem;
};
QT_END_NAMESPACE
#endif // DIRECTEDVECTOR_H
@@ -0,0 +1,261 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
#include "qquicktrailemitter_p.h"
#include <private/qqmlglobal_p.h>
#include <private/qquickv4particledata_p.h>
#include <QtCore/qrandom.h>
#include <cmath>
QT_BEGIN_NAMESPACE
/*!
\qmltype TrailEmitter
\nativetype QQuickTrailEmitter
\inqmlmodule QtQuick.Particles
\inherits Emitter
\brief Emits logical particles from other logical particles.
\ingroup qtquick-particles
This element emits logical particles into the ParticleSystem, with the
starting positions based on those of other logical particles.
*/
QQuickTrailEmitter::QQuickTrailEmitter(QQuickItem *parent) :
QQuickParticleEmitter(parent)
, m_particlesPerParticlePerSecond(0)
, m_lastTimeStamp(0)
, m_emitterXVariation(0)
, m_emitterYVariation(0)
, m_followCount(0)
, m_emissionExtruder(nullptr)
, m_defaultEmissionExtruder(new QQuickParticleExtruder(this))
{
//TODO: If followed increased their size
connect(this, &QQuickTrailEmitter::followChanged,
this, &QQuickTrailEmitter::recalcParticlesPerSecond);
connect(this, &QQuickTrailEmitter::particleDurationChanged,
this, &QQuickTrailEmitter::recalcParticlesPerSecond);
connect(this, &QQuickTrailEmitter::particlesPerParticlePerSecondChanged,
this, &QQuickTrailEmitter::recalcParticlesPerSecond);
}
/*!
\qmlproperty string QtQuick.Particles::TrailEmitter::follow
The type of logical particle which this is emitting from.
*/
/*!
\qmlproperty real QtQuick.Particles::TrailEmitter::velocityFromMovement
If this value is non-zero, then any movement of the emitter will provide additional
starting velocity to the particles based on the movement. The additional vector will be the
same angle as the emitter's movement, with a magnitude that is the magnitude of the emitters
movement multiplied by velocityFromMovement.
Default value is 0.
*/
/*!
\qmlproperty Shape QtQuick.Particles::TrailEmitter::emitShape
As the area of a TrailEmitter is the area it follows, a separate shape can be provided
to be the shape it emits out of. This shape has width and height specified by emitWidth
and emitHeight, and is centered on the followed particle's position.
The default shape is a filled Rectangle.
*/
/*!
\qmlproperty real QtQuick.Particles::TrailEmitter::emitWidth
The width in pixels the emitShape is scaled to. If set to TrailEmitter.ParticleSize,
the width will be the current size of the particle being followed.
Default is 0.
*/
/*!
\qmlproperty real QtQuick.Particles::TrailEmitter::emitHeight
The height in pixels the emitShape is scaled to. If set to TrailEmitter.ParticleSize,
the height will be the current size of the particle being followed.
Default is 0.
*/
/*!
\qmlproperty real QtQuick.Particles::TrailEmitter::emitRatePerParticle
*/
/*!
\qmlsignal QtQuick.Particles::TrailEmitter::emitFollowParticles(Array particles, Particle followed)
This signal is emitted when particles are emitted from the \a followed particle. \a particles contains an array of particle objects which can be directly manipulated.
If you use this signal handler, emitParticles will not be emitted.
*/
bool QQuickTrailEmitter::isEmitFollowConnected()
{
IS_SIGNAL_CONNECTED(
this, QQuickTrailEmitter, emitFollowParticles,
(const QList<QQuickV4ParticleData> &, const QQuickV4ParticleData &));
}
void QQuickTrailEmitter::recalcParticlesPerSecond(){
if (!m_system)
return;
m_followCount = m_system->groupData[m_system->groupIds[m_follow]]->size();
if (!m_followCount){
setParticlesPerSecond(1);//XXX: Fix this horrendous hack, needed so they aren't turned off from start (causes crashes - test that when gone you don't crash with 0 PPPS)
}else{
setParticlesPerSecond(m_particlesPerParticlePerSecond * m_followCount);
m_lastEmission.resize(m_followCount);
m_lastEmission.fill(m_lastTimeStamp);
}
}
void QQuickTrailEmitter::reset()
{
m_followCount = 0;
}
void QQuickTrailEmitter::emitWindow(int timeStamp)
{
if (m_system == nullptr)
return;
if (!m_enabled && !m_pulseLeft && m_burstQueue.isEmpty())
return;
if (m_followCount != m_system->groupData[m_system->groupIds[m_follow]]->size()){
qreal oldPPS = m_particlesPerSecond;
recalcParticlesPerSecond();
if (m_particlesPerSecond != oldPPS)
return;//system may need to update
}
if (m_pulseLeft){
m_pulseLeft -= timeStamp - m_lastTimeStamp * 1000.;
if (m_pulseLeft < 0){
timeStamp += m_pulseLeft;
m_pulseLeft = 0;
}
}
//TODO: Implement startTime and velocityFromMovement
qreal time = timeStamp / 1000.;
qreal particleRatio = 1. / m_particlesPerParticlePerSecond;
qreal pt;
qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
//Have to map it into this system, because particlesystem automaps it back
QPointF offset = m_system->mapFromItem(this, QPointF(0, 0));
qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
int gId = m_system->groupIds[m_follow];
int gId2 = groupId();
for (int i=0; i<m_system->groupData[gId]->data.size(); i++) {
QQuickParticleData *d = m_system->groupData[gId]->data[i];
if (!d->stillAlive(m_system)){
m_lastEmission[i] = time; //Should only start emitting when it returns to life
continue;
}
pt = m_lastEmission[i];
if (pt < d->t)
pt = d->t;
if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
pt = time - maxLife;
if ((width() || height()) && !effectiveExtruder()->contains(QRectF(offset.x(), offset.y(), width(), height()),
QPointF(d->curX(m_system), d->curY(m_system)))) {
m_lastEmission[d->index] = time;//jump over this time period without emitting, because it's outside
continue;
}
QList<QQuickParticleData*> toEmit;
while (pt < time || !m_burstQueue.isEmpty()){
QQuickParticleData* datum = m_system->newDatum(gId2, !m_overwrite);
if (datum){//else, skip this emission
// Particle timestamp
datum->t = pt;
datum->lifeSpan =
(m_particleDuration
+ (QRandomGenerator::global()->bounded((m_particleDurationVariation*2) + 1) - m_particleDurationVariation))
/ 1000.0;
// Particle position
// Note that burst location doesn't get used for follow emitter
qreal followT = pt - d->t;
qreal followT2 = followT * followT * 0.5;
qreal eW = m_emitterXVariation < 0 ? d->curSize(m_system) : m_emitterXVariation;
qreal eH = m_emitterYVariation < 0 ? d->curSize(m_system) : m_emitterYVariation;
//Subtract offset, because PS expects this in emitter coordinates
QRectF boundsRect(d->x - offset.x() + d->vx * followT + d->ax * followT2 - eW/2,
d->y - offset.y() + d->vy * followT + d->ay * followT2 - eH/2,
eW, eH);
QQuickParticleExtruder* effectiveEmissionExtruder = m_emissionExtruder ? m_emissionExtruder : m_defaultEmissionExtruder;
const QPointF &newPos = effectiveEmissionExtruder->extrude(boundsRect);
datum->x = newPos.x();
datum->y = newPos.y();
// Particle velocity
const QPointF &velocity = m_velocity->sample(newPos);
datum->vx = velocity.x()
+ m_velocity_from_movement * d->vx;
datum->vy = velocity.y()
+ m_velocity_from_movement * d->vy;
// Particle acceleration
const QPointF &accel = m_acceleration->sample(newPos);
datum->ax = accel.x();
datum->ay = accel.y();
// Particle size
float sizeVariation = -m_particleSizeVariation
+ QRandomGenerator::global()->generateDouble() * m_particleSizeVariation * 2;
float size = qMax((qreal)0.0, m_particleSize + sizeVariation);
float endSize = qMax((qreal)0.0, sizeAtEnd + sizeVariation);
datum->size = size * float(m_enabled);
datum->endSize = endSize * float(m_enabled);
toEmit << datum;
m_system->emitParticle(datum, this);
}
if (!m_burstQueue.isEmpty()){
m_burstQueue.first().first--;
if (m_burstQueue.first().first <= 0)
m_burstQueue.pop_front();
}else{
pt += particleRatio;
}
}
foreach (QQuickParticleData* d, toEmit)
m_system->emitParticle(d, this);
if (isEmitConnected() || isEmitFollowConnected()) {
QList<QQuickV4ParticleData> particles;
particles.reserve(toEmit.size());
for (QQuickParticleData *particle : std::as_const(toEmit))
particles.push_back(particle->v4Value(m_system));
if (isEmitFollowConnected()) {
//A chance for many arbitrary JS changes
emit emitFollowParticles(particles, d->v4Value(m_system));
} else if (isEmitConnected()) {
emit emitParticles(particles);//A chance for arbitrary JS changes
}
}
m_lastEmission[d->index] = pt;
}
m_lastTimeStamp = time;
}
QT_END_NAMESPACE
#include "moc_qquicktrailemitter_p.cpp"
@@ -0,0 +1,141 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef FOLLOWEMITTER_H
#define FOLLOWEMITTER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleemitter_p.h"
#include "qquickparticleaffector_p.h"
QT_BEGIN_NAMESPACE
class Q_QUICKPARTICLES_EXPORT QQuickTrailEmitter : public QQuickParticleEmitter
{
Q_OBJECT
Q_PROPERTY(QString follow READ follow WRITE setFollow NOTIFY followChanged)
Q_PROPERTY(int emitRatePerParticle READ particlesPerParticlePerSecond WRITE setParticlesPerParticlePerSecond NOTIFY particlesPerParticlePerSecondChanged)
Q_PROPERTY(QQuickParticleExtruder* emitShape READ emissonShape WRITE setEmissionShape NOTIFY emissionShapeChanged)
Q_PROPERTY(qreal emitHeight READ emitterYVariation WRITE setEmitterYVariation NOTIFY emitterYVariationChanged)
Q_PROPERTY(qreal emitWidth READ emitterXVariation WRITE setEmitterXVariation NOTIFY emitterXVariationChanged)
QML_NAMED_ELEMENT(TrailEmitter)
QML_ADDED_IN_VERSION(2, 0)
public:
enum EmitSize {
ParticleSize = -2//Anything less than 0 will do
};
Q_ENUM(EmitSize)
explicit QQuickTrailEmitter(QQuickItem *parent = nullptr);
void emitWindow(int timeStamp) override;
void reset() override;
int particlesPerParticlePerSecond() const
{
return m_particlesPerParticlePerSecond;
}
qreal emitterXVariation() const
{
return m_emitterXVariation;
}
qreal emitterYVariation() const
{
return m_emitterYVariation;
}
QString follow() const
{
return m_follow;
}
QQuickParticleExtruder* emissonShape() const
{
return m_emissionExtruder;
}
Q_SIGNALS:
void emitFollowParticles(
const QList<QQuickV4ParticleData> &particles,
const QQuickV4ParticleData &followed);
void particlesPerParticlePerSecondChanged(int arg);
void emitterXVariationChanged(qreal arg);
void emitterYVariationChanged(qreal arg);
void followChanged(const QString &arg);
void emissionShapeChanged(QQuickParticleExtruder* arg);
public Q_SLOTS:
void setParticlesPerParticlePerSecond(int arg)
{
if (m_particlesPerParticlePerSecond != arg) {
m_particlesPerParticlePerSecond = arg;
Q_EMIT particlesPerParticlePerSecondChanged(arg);
}
}
void setEmitterXVariation(qreal arg)
{
if (m_emitterXVariation != arg) {
m_emitterXVariation = arg;
Q_EMIT emitterXVariationChanged(arg);
}
}
void setEmitterYVariation(qreal arg)
{
if (m_emitterYVariation != arg) {
m_emitterYVariation = arg;
Q_EMIT emitterYVariationChanged(arg);
}
}
void setFollow(const QString &arg)
{
if (m_follow != arg) {
m_follow = arg;
Q_EMIT followChanged(arg);
}
}
void setEmissionShape(QQuickParticleExtruder* arg)
{
if (m_emissionExtruder != arg) {
m_emissionExtruder = arg;
Q_EMIT emissionShapeChanged(arg);
}
}
private Q_SLOTS:
void recalcParticlesPerSecond();
private:
QList<qreal> m_lastEmission;
int m_particlesPerParticlePerSecond;
qreal m_lastTimeStamp;
qreal m_emitterXVariation;
qreal m_emitterYVariation;
QString m_follow;
int m_followCount;
QQuickParticleExtruder* m_emissionExtruder;
QQuickParticleExtruder* m_defaultEmissionExtruder;
bool isEmitFollowConnected();
};
QT_END_NAMESPACE
#endif // FOLLOWEMITTER_H
@@ -0,0 +1,173 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
#include "qquickturbulence_p.h"
#include "qquickparticlepainter_p.h"//TODO: Why was this needed again?
#include <cmath>
#include <cstdlib>
#include <QDebug>
#include <QQmlFile>
QT_BEGIN_NAMESPACE
/*!
\qmltype Turbulence
\nativetype QQuickTurbulenceAffector
\inqmlmodule QtQuick.Particles
\ingroup qtquick-particles
\inherits ParticleAffector
\brief Provides fluid-like forces from a noise image.
The Turbulence Element scales the noise source over the area it affects,
and uses the curl of that source to generate force vectors.
Turbulence requires a fixed size. Unlike other affectors, a 0x0 Turbulence element
will affect no particles.
The source should be relatively smooth black and white noise, such as perlin noise.
*/
/*!
\qmlproperty real QtQuick.Particles::Turbulence::strength
The magnitude of the velocity vector at any point varies between zero and
the square root of two. It will then be multiplied by strength to get the
velocity per second for the particles affected by the turbulence.
*/
/*!
\qmlproperty url QtQuick.Particles::Turbulence::noiseSource
The source image to generate the turbulence from. It will be scaled to the size of the element,
so equal or larger sizes will give better results. Tweaking this image is the only way to tweak
behavior such as where vortices are or how many exist.
The source should be a relatively smooth black and white noise image, such as perlin noise.
A default image will be used if none is provided.
*/
QQuickTurbulenceAffector::QQuickTurbulenceAffector(QQuickItem *parent) :
QQuickParticleAffector(parent),
m_strength(10), m_gridSize(0), m_field(nullptr), m_vectorField(nullptr), m_inited(false)
{
}
void QQuickTurbulenceAffector::geometryChange(const QRectF &, const QRectF &)
{
initializeGrid();
}
QQuickTurbulenceAffector::~QQuickTurbulenceAffector()
{
if (m_field) {
for (int i=0; i<m_gridSize; i++)
free(m_field[i]);
free(m_field);
}
if (m_vectorField) {
for (int i=0; i<m_gridSize; i++)
free(m_vectorField[i]);
free(m_vectorField);
}
}
void QQuickTurbulenceAffector::initializeGrid()
{
if (!m_inited)
return;
int arg = qMax(width(), height());
if (m_gridSize != arg) {
if (m_field){ //deallocate and then reallocate grid
for (int i=0; i<m_gridSize; i++)
free(m_field[i]);
free(m_field);
}
if (m_vectorField) {
for (int i=0; i<m_gridSize; i++)
free(m_vectorField[i]);
free(m_vectorField);
}
m_gridSize = arg;
}
m_field = (qreal**)malloc(m_gridSize * sizeof(qreal*));
for (int i=0; i<m_gridSize; i++)
m_field[i] = (qreal*)malloc(m_gridSize * sizeof(qreal));
m_vectorField = (QPointF**)malloc(m_gridSize * sizeof(QPointF*));
for (int i=0; i<m_gridSize; i++)
m_vectorField[i] = (QPointF*)malloc(m_gridSize * sizeof(QPointF));
QImage image;
if (!m_noiseSource.isEmpty())
image = QImage(QQmlFile::urlToLocalFileOrQrc(m_noiseSource)).scaled(QSize(m_gridSize, m_gridSize));
if (image.isNull())
image = QImage(QStringLiteral(":particleresources/noise.png")).scaled(QSize(m_gridSize, m_gridSize));
for (int i=0; i<m_gridSize; i++)
for (int j=0; j<m_gridSize; j++)
m_field[i][j] = qGray(image.pixel(QPoint(i,j)));
for (int i=0; i<m_gridSize; i++){
for (int j=0; j<m_gridSize; j++){
m_vectorField[i][j].setX(boundsRespectingField(i-1,j) - boundsRespectingField(i,j));
m_vectorField[i][j].setY(boundsRespectingField(i,j) - boundsRespectingField(i,j-1));
}
}
}
qreal QQuickTurbulenceAffector::boundsRespectingField(int x, int y)
{
if (x < 0)
x = 0;
if (x >= m_gridSize)
x = m_gridSize - 1;
if (y < 0)
y = 0;
if (y >= m_gridSize)
y = m_gridSize - 1;
return m_field[x][y];
}
void QQuickTurbulenceAffector::ensureInit()
{
if (m_inited)
return;
m_inited = true;
initializeGrid();
}
void QQuickTurbulenceAffector::affectSystem(qreal dt)
{
if (!m_system || !m_enabled)
return;
ensureInit();
if (!m_gridSize)
return;
updateOffsets();//### Needed if an ancestor is transformed.
QRect boundsRect(0,0,m_gridSize,m_gridSize);
for (QQuickParticleGroupData *gd : m_system->groupData) {
if (!activeGroup(gd->index))
continue;
foreach (QQuickParticleData *d, gd->data){
if (!shouldAffect(d))
continue;
QPoint pos = (QPointF(d->curX(m_system), d->curY(m_system)) - m_offset).toPoint();
if (!boundsRect.contains(pos,true))//Need to redo bounds checking due to quantization.
continue;
qreal fx = 0.0;
qreal fy = 0.0;
fx += m_vectorField[pos.x()][pos.y()].x() * m_strength;
fy += m_vectorField[pos.x()][pos.y()].y() * m_strength;
if (fx || fy){
d->setInstantaneousVX(d->curVX(m_system)+ fx * dt, m_system);
d->setInstantaneousVY(d->curVY(m_system)+ fy * dt, m_system);
postAffect(d);
}
}
}
}
QT_END_NAMESPACE
#include "moc_qquickturbulence_p.cpp"
@@ -0,0 +1,87 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef TURBULENCEAFFECTOR_H
#define TURBULENCEAFFECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qquickparticleaffector_p.h"
#include <QQmlListProperty>
QT_BEGIN_NAMESPACE
class QQuickParticlePainter;
class Q_QUICKPARTICLES_EXPORT QQuickTurbulenceAffector : public QQuickParticleAffector
{
Q_OBJECT
Q_PROPERTY(qreal strength READ strength WRITE setStrength NOTIFY strengthChanged)
Q_PROPERTY(QUrl noiseSource READ noiseSource WRITE setNoiseSource NOTIFY noiseSourceChanged)
QML_NAMED_ELEMENT(Turbulence)
QML_ADDED_IN_VERSION(2, 0)
public:
explicit QQuickTurbulenceAffector(QQuickItem *parent = nullptr);
~QQuickTurbulenceAffector();
void affectSystem(qreal dt) override;
qreal strength() const
{
return m_strength;
}
QUrl noiseSource() const
{
return m_noiseSource;
}
Q_SIGNALS:
void strengthChanged(qreal arg);
void noiseSourceChanged(const QUrl &arg);
public Q_SLOTS:
void setStrength(qreal arg)
{
if (m_strength != arg) {
m_strength = arg;
Q_EMIT strengthChanged(arg);
}
}
void setNoiseSource(const QUrl &arg)
{
if (m_noiseSource != arg) {
m_noiseSource = arg;
Q_EMIT noiseSourceChanged(arg);
initializeGrid();
}
}
protected:
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
private:
void ensureInit();
void mapUpdate();
void initializeGrid();
qreal boundsRespectingField(int x, int y);
qreal m_strength;
int m_gridSize;
qreal** m_field;
QPointF** m_vectorField;
bool m_inited;
QUrl m_noiseSource;
};
QT_END_NAMESPACE
#endif // TURBULENCEAFFECTOR_H
@@ -0,0 +1,233 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <math.h>
#include "qquickv4particledata_p.h"
#include <QDebug>
#include <private/qv4engine_p.h>
#include <private/qv4functionobject_p.h>
#include <QtCore/private/qnumeric_p.h>
QT_BEGIN_NAMESPACE
/*!
\qmltype Particle
\inqmlmodule QtQuick.Particles
\brief Represents particles manipulated by emitters and affectors.
\ingroup qtquick-particles
Particle elements are always managed internally by the ParticleSystem and cannot be created in QML.
However, sometimes they are exposed via signals so as to allow arbitrary changes to the particle state
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::initialX
The x coordinate of the particle at the beginning of its lifetime.
The method of simulation prefers to have the initial values changed, rather
than determining and changing the value at a given time. Change initial
values in CustomEmitters instead of the current values.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::initialVX
The x velocity of the particle at the beginning of its lifetime.
The method of simulation prefers to have the initial values changed, rather
than determining and changing the value at a given time. Change initial
values in CustomEmitters instead of the current values.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::initialAX
The x acceleration of the particle at the beginning of its lifetime.
The method of simulation prefers to have the initial values changed, rather
than determining and changing the value at a given time. Change initial
values in CustomEmitters instead of the current values.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::initialY
The y coordinate of the particle at the beginning of its lifetime.
The method of simulation prefers to have the initial values changed, rather
than determining and changing the value at a given time. Change initial
values in CustomEmitters instead of the current values.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::initialVY
The y velocity of the particle at the beginning of its lifetime.
The method of simulation prefers to have the initial values changed, rather
than determining and changing the value at a given time. Change initial
values in CustomEmitters instead of the current values.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::initialAY
The y acceleration of the particle at the beginning of its lifetime.
The method of simulation prefers to have the initial values changed, rather
than determining and changing the value at a given time. Change initial
values in CustomEmitters instead of the current values.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::x
The current x coordinate of the particle.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::vx
The current x velocity of the particle.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::ax
The current x acceleration of the particle.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::y
The current y coordinate of the particle.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::vy
The current y velocity of the particle.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::ay
The current y acceleration of the particle.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::t
The time, in seconds since the beginning of the simulation, that the particle was born.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::startSize
The size in pixels that the particle image is at the start
of its life.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::endSize
The size in pixels that the particle image is at the end
of its life. If this value is less than 0, then it is
disregarded and the particle will have its startSize for the
entire lifetime.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::lifeSpan
The time in seconds that the particle will live for.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::rotation
Degrees clockwise that the particle image is rotated at
the beginning of its life.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::rotationVelocity
Degrees clockwise per second that the particle image is rotated at while alive.
*/
/*!
\qmlproperty bool QtQuick.Particles::Particle::autoRotate
If autoRotate is true, then the particle's rotation will be
set so that it faces the direction of travel, plus any
rotation from the rotation or rotationVelocity properties.
*/
/*!
\qmlproperty bool QtQuick.Particles::Particle::update
Inside an Affector, the changes made to the particle will only be
applied if update is set to true.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::xDeformationVectorX
The x component of the deformation vector along the X axis. ImageParticle
can draw particles across non-square shapes. It will draw the texture rectangle
across the parallelogram drawn with the x and y deformation vectors.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::yDeformationVectorX
The y component of the deformation vector along the X axis. ImageParticle
can draw particles across non-square shapes. It will draw the texture rectangle
across the parallelogram drawn with the x and y deformation vectors.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::xDeformationVectorY
The x component of the deformation vector along the X axis. ImageParticle
can draw particles across non-square shapes. It will draw the texture rectangle
across the parallelogram drawn with the x and y deformation vectors.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::yDeformationVectorY
The y component of the deformation vector along the Y axis. ImageParticle
can draw particles across non-square shapes. It will draw the texture rectangle
across the parallelogram drawn with the x and y deformation vectors.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::red
ImageParticle can draw colorized particles. When it does so, red is used
as the red channel of the color applied to the source image.
Values are from 0.0 to 1.0.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::green
ImageParticle can draw colorized particles. When it does so, green is used
as the green channel of the color applied to the source image.
Values are from 0.0 to 1.0.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::blue
ImageParticle can draw colorized particles. When it does so, blue is used
as the blue channel of the color applied to the source image.
Values are from 0.0 to 1.0.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::alpha
ImageParticle can draw colorized particles. When it does so, alpha is used
as the alpha channel of the color applied to the source image.
Values are from 0.0 to 1.0.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::lifeLeft
The time in seconds that the particle has left to live at
the current point in time.
*/
/*!
\qmlproperty real QtQuick.Particles::Particle::currentSize
The currentSize of the particle, interpolating between startSize and endSize based on the currentTime.
*/
QT_END_NAMESPACE
@@ -0,0 +1,127 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QQuickV8PARTICLEDATA_H
#define QQuickV8PARTICLEDATA_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <private/qquickparticlesystem_p.h>
#include <QtQml/qqml.h>
QT_BEGIN_NAMESPACE
class QQuickV4ParticleData
{
Q_GADGET
QML_VALUE_TYPE(particle)
QML_ADDED_IN_VERSION(6, 7)
#define Q_QUICK_PARTICLE_ACCESSOR(TYPE, VARIABLE, NAME) \
Q_PROPERTY(TYPE NAME READ NAME WRITE set_ ## NAME FINAL) \
TYPE NAME() const { return datum ? datum->VARIABLE : TYPE(); } \
void set_ ## NAME(TYPE a) { if (datum) datum->VARIABLE = a; }
Q_QUICK_PARTICLE_ACCESSOR(float, x, initialX)
Q_QUICK_PARTICLE_ACCESSOR(float, vx, initialVX)
Q_QUICK_PARTICLE_ACCESSOR(float, ax, initialAX)
Q_QUICK_PARTICLE_ACCESSOR(float, y, initialY)
Q_QUICK_PARTICLE_ACCESSOR(float, vy, initialVY)
Q_QUICK_PARTICLE_ACCESSOR(float, ay, initialAY)
Q_QUICK_PARTICLE_ACCESSOR(float, t, t)
Q_QUICK_PARTICLE_ACCESSOR(float, size, startSize)
Q_QUICK_PARTICLE_ACCESSOR(float, endSize, endSize)
Q_QUICK_PARTICLE_ACCESSOR(float, lifeSpan, lifeSpan)
Q_QUICK_PARTICLE_ACCESSOR(float, rotation, rotation)
Q_QUICK_PARTICLE_ACCESSOR(float, rotationVelocity, rotationVelocity)
Q_QUICK_PARTICLE_ACCESSOR(bool, autoRotate, autoRotate)
Q_QUICK_PARTICLE_ACCESSOR(bool, update, update)
Q_QUICK_PARTICLE_ACCESSOR(float, xx, xDeformationVectorX)
Q_QUICK_PARTICLE_ACCESSOR(float, yx, yDeformationVectorX)
Q_QUICK_PARTICLE_ACCESSOR(float, xy, xDeformationVectorY)
Q_QUICK_PARTICLE_ACCESSOR(float, yy, yDeformationVectorY)
// Undocumented?
Q_QUICK_PARTICLE_ACCESSOR(float, animIdx, animationIndex)
Q_QUICK_PARTICLE_ACCESSOR(float, frameDuration, frameDuration)
Q_QUICK_PARTICLE_ACCESSOR(float, frameAt, frameAt)
Q_QUICK_PARTICLE_ACCESSOR(float, frameCount, frameCount)
Q_QUICK_PARTICLE_ACCESSOR(float, animT, animationT)
#undef Q_QUICK_PARTICLE_ACCESSOR
#define Q_QUICK_PARTICLE_SYSTEM_ACCESSOR(GETTER, SETTER, NAME) \
Q_PROPERTY(float NAME READ NAME WRITE set_ ## NAME) \
float NAME() const { return (datum && particleSystem) ? datum->GETTER(particleSystem) : 0; } \
void set_ ## NAME(float a) { if (datum && particleSystem) datum->SETTER(a, particleSystem); }
Q_QUICK_PARTICLE_SYSTEM_ACCESSOR(curX, setInstantaneousX, x)
Q_QUICK_PARTICLE_SYSTEM_ACCESSOR(curVX, setInstantaneousVX, vx)
Q_QUICK_PARTICLE_SYSTEM_ACCESSOR(curAX, setInstantaneousAX, ax)
Q_QUICK_PARTICLE_SYSTEM_ACCESSOR(curY, setInstantaneousY, y)
Q_QUICK_PARTICLE_SYSTEM_ACCESSOR(curVY, setInstantaneousVY, vy)
Q_QUICK_PARTICLE_SYSTEM_ACCESSOR(curAY, setInstantaneousAY, ay)
#undef Q_QUICK_PARTICLE_SYSTEM_ACCESSOR
#define Q_QUICK_PARTICLE_COLOR_ACCESSOR(VAR, NAME) \
Q_PROPERTY(float NAME READ NAME WRITE set_ ## NAME) \
float NAME() const { return datum ? datum->color.VAR / 255.0 : 0.0; } \
void set_ ## NAME(float a)\
{\
if (datum)\
datum->color.VAR = qMin(255, qMax(0, (int)::floor(a * 255.0)));\
}
Q_QUICK_PARTICLE_COLOR_ACCESSOR(r, red)
Q_QUICK_PARTICLE_COLOR_ACCESSOR(g, green)
Q_QUICK_PARTICLE_COLOR_ACCESSOR(b, blue)
Q_QUICK_PARTICLE_COLOR_ACCESSOR(a, alpha)
#undef Q_QUICK_PARTICLE_COLOR_ACCESSOR
Q_PROPERTY(float lifeLeft READ lifeLeft)
Q_PROPERTY(float currentSize READ currentSize)
public:
QQuickV4ParticleData() = default;
QQuickV4ParticleData(QQuickParticleData *datum, QQuickParticleSystem *system)
: datum(datum)
, particleSystem(system)
{}
Q_INVOKABLE void discard()
{
if (datum)
datum->lifeSpan = 0;
}
float lifeLeft() const
{
return (datum && particleSystem) ? datum->lifeLeft(particleSystem) : 0.0;
}
float currentSize() const
{
return (datum && particleSystem) ? datum->curSize(particleSystem) : 0.0;
}
private:
QQuickParticleData *datum = nullptr;
QQuickParticleSystem *particleSystem = nullptr;
};
QT_END_NAMESPACE
#endif
@@ -0,0 +1,148 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquickwander_p.h"
#include "qquickparticlesystem_p.h"//for ParticlesVertices
#include <QRandomGenerator>
QT_BEGIN_NAMESPACE
/*!
\qmltype Wander
\nativetype QQuickWanderAffector
\inqmlmodule QtQuick.Particles
\ingroup qtquick-particles
\inherits ParticleAffector
\brief For applying random particle trajectory.
*/
/*!
\qmlproperty real QtQuick.Particles::Wander::pace
Maximum attribute change per second.
*/
/*!
\qmlproperty real QtQuick.Particles::Wander::xVariance
Maximum attribute x value (as a result of Wander).
If unset, Wander will not affect x values.
*/
/*!
\qmlproperty real QtQuick.Particles::Wander::yVariance
Maximum attribute y value (as a result of Wander).
If unset, Wander will not affect y values.
*/
/*!
\qmlproperty AffectableParameter QtQuick.Particles::Wander::affectedParameter
What attribute of particles is directly affected.
\list
\li PointAttractor.Position
\li PointAttractor.Velocity
\li PointAttractor.Acceleration
\endlist
*/
QQuickWanderAffector::QQuickWanderAffector(QQuickItem *parent) :
QQuickParticleAffector(parent), m_xVariance(0), m_yVariance(0), m_pace(0)
, m_affectedParameter(Velocity)
{
m_needsReset = true;
}
QQuickWanderAffector::~QQuickWanderAffector()
{
for (QHash<int, WanderData*>::const_iterator iter=m_wanderData.constBegin();
iter != m_wanderData.constEnd(); ++iter)
delete (*iter);
}
WanderData* QQuickWanderAffector::getData(int idx)
{
if (m_wanderData.contains(idx))
return m_wanderData[idx];
WanderData* d = new WanderData;
d->x_vel = 0;
d->y_vel = 0;
d->x_peak = m_xVariance;
d->y_peak = m_yVariance;
d->x_var = m_pace * QRandomGenerator::global()->generateDouble();
d->y_var = m_pace * QRandomGenerator::global()->generateDouble();
m_wanderData.insert(idx, d);
return d;
}
// TODO: see below
//void QQuickWanderAffector::reset(int systemIdx)
//{
// if (m_wanderData.contains(systemIdx))
// delete m_wanderData[systemIdx];
// m_wanderData.remove(systemIdx);
//}
bool QQuickWanderAffector::affectParticle(QQuickParticleData* data, qreal dt)
{
/*TODO: Add a mode which does basically this - picking a direction, going in it (random velocity) and then going back
WanderData* d = getData(data->systemIndex);
if (m_xVariance != 0.) {
if ((d->x_vel > d->x_peak && d->x_var > 0.0) || (d->x_vel < -d->x_peak && d->x_var < 0.0)) {
d->x_var = -d->x_var;
d->x_peak = m_xVariance + m_xVariance * QRandomGenerator::global()->generateDouble();
}
d->x_vel += d->x_var * dt;
}
qreal dx = dt * d->x_vel;
if (m_yVariance != 0.) {
if ((d->y_vel > d->y_peak && d->y_var > 0.0) || (d->y_vel < -d->y_peak && d->y_var < 0.0)) {
d->y_var = -d->y_var;
d->y_peak = m_yVariance + m_yVariance * QRandomGenerator::global()->generateDouble();
}
d->y_vel += d->y_var * dt;
}
qreal dy = dt * d->x_vel;
//### Should we be amending vel instead?
ParticleVertex* p = &(data->pv);
p->x += dx;
p->y += dy;
return true;
*/
qreal dx = dt * m_pace * (2 * QRandomGenerator::global()->generateDouble() - 1);
qreal dy = dt * m_pace * (2 * QRandomGenerator::global()->generateDouble() - 1);
qreal newX, newY;
switch (m_affectedParameter){
case Position:
newX = data->curX(m_system) + dx;
if (m_xVariance > qAbs(newX) )
data->x += dx;
newY = data->curY(m_system) + dy;
if (m_yVariance > qAbs(newY) )
data->y += dy;
break;
default:
case Velocity:
newX = data->curVX(m_system) + dx;
if (m_xVariance > qAbs(newX))
data->setInstantaneousVX(newX, m_system);
newY = data->curVY(m_system) + dy;
if (m_yVariance > qAbs(newY))
data->setInstantaneousVY(newY, m_system);
break;
case Acceleration:
newX = data->ax + dx;
if (m_xVariance > qAbs(newX))
data->setInstantaneousAX(newX, m_system);
newY = data->ay + dy;
if (m_yVariance > qAbs(newY))
data->setInstantaneousAY(newY, m_system);
break;
}
return true;
}
QT_END_NAMESPACE
#include "moc_qquickwander_p.cpp"
@@ -0,0 +1,131 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef WANDERAFFECTOR_H
#define WANDERAFFECTOR_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QHash>
#include "qquickparticleaffector_p.h"
QT_BEGIN_NAMESPACE
struct WanderData{
qreal x_vel;
qreal y_vel;
qreal x_peak;
qreal x_var;
qreal y_peak;
qreal y_var;
};
class Q_QUICKPARTICLES_EXPORT QQuickWanderAffector : public QQuickParticleAffector
{
Q_OBJECT
Q_PROPERTY(qreal pace READ pace WRITE setPace NOTIFY paceChanged)
Q_PROPERTY(qreal xVariance READ xVariance WRITE setXVariance NOTIFY xVarianceChanged)
Q_PROPERTY(qreal yVariance READ yVariance WRITE setYVariance NOTIFY yVarianceChanged)
Q_PROPERTY(AffectableParameters affectedParameter READ affectedParameter WRITE setAffectedParameter NOTIFY affectedParameterChanged)
QML_NAMED_ELEMENT(Wander)
QML_ADDED_IN_VERSION(2, 0)
public:
enum AffectableParameters {
Position,
Velocity,
Acceleration
};
Q_ENUM(AffectableParameters)
explicit QQuickWanderAffector(QQuickItem *parent = nullptr);
~QQuickWanderAffector();
// virtual void reset(int systemIdx);
qreal xVariance() const
{
return m_xVariance;
}
qreal yVariance() const
{
return m_yVariance;
}
qreal pace() const
{
return m_pace;
}
AffectableParameters affectedParameter() const
{
return m_affectedParameter;
}
protected:
bool affectParticle(QQuickParticleData *d, qreal dt) override;
Q_SIGNALS:
void xVarianceChanged(qreal arg);
void yVarianceChanged(qreal arg);
void paceChanged(qreal arg);
void affectedParameterChanged(AffectableParameters arg);
public Q_SLOTS:
void setXVariance(qreal arg)
{
if (m_xVariance != arg) {
m_xVariance = arg;
Q_EMIT xVarianceChanged(arg);
}
}
void setYVariance(qreal arg)
{
if (m_yVariance != arg) {
m_yVariance = arg;
Q_EMIT yVarianceChanged(arg);
}
}
void setPace(qreal arg)
{
if (m_pace != arg) {
m_pace = arg;
Q_EMIT paceChanged(arg);
}
}
void setAffectedParameter(AffectableParameters arg)
{
if (m_affectedParameter != arg) {
m_affectedParameter = arg;
Q_EMIT affectedParameterChanged(arg);
}
}
private:
WanderData* getData(int idx);
QHash<int, WanderData*> m_wanderData;
qreal m_xVariance;
qreal m_yVariance;
qreal m_pace;
AffectableParameters m_affectedParameter;
};
QT_END_NAMESPACE
#endif // WANDERAFFECTOR_H
@@ -0,0 +1,21 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QTQUICKPARTICLESGLOBAL_P_H
#define QTQUICKPARTICLESGLOBAL_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtCore/qglobal.h>
#include <QtQuickParticles/qtquickparticlesexports.h>
#endif // QTQUICKPARTICLESGLOBAL_P_H
@@ -0,0 +1,63 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#version 440
#if defined(TABLE)
layout(location = 0) in vec2 tt;
#endif
#if defined(SPRITE)
layout(location = 1) in vec4 fTexS;
#elif !defined(POINT)
layout(location = 1) in vec2 fTex;
#endif
#if defined(COLOR)
layout(location = 2) in vec4 fColor;
#else
layout(location = 2) in float fFade;
#endif
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
#if QSHADER_VIEW_COUNT >= 2
mat4 matrix[QSHADER_VIEW_COUNT];
#else
mat4 matrix;
#endif
float opacity;
float entry;
float timestamp;
float dpr;
float sizetable[64];
float opacitytable[64];
} ubuf;
layout(binding = 1) uniform sampler2D _qt_texture;
#if defined(TABLE) || defined(SPRITE)
layout(binding = 2) uniform sampler2D colortable;
#endif
void main()
{
#if defined(SPRITE)
fragColor = mix(texture(_qt_texture, fTexS.xy), texture(_qt_texture, fTexS.zw), tt.y)
* fColor
* texture(colortable, tt)
* ubuf.opacity;
#elif defined(TABLE)
fragColor = texture(_qt_texture, fTex)
* fColor
* texture(colortable, tt)
* ubuf.opacity;
#elif !defined(POINT)
fragColor = texture(_qt_texture, fTex) * fColor * ubuf.opacity;
#elif defined(COLOR)
fragColor = texture(_qt_texture, gl_PointCoord) * fColor * ubuf.opacity;
#else // simple point
fragColor = texture(_qt_texture, gl_PointCoord) * fFade * ubuf.opacity;
#endif
}
@@ -0,0 +1,169 @@
#version 440
layout(location = 1) in vec4 vData; // x = time, y = lifeSpan, z = size, w = endSize
layout(location = 2) in vec4 vVec; // x,y = constant velocity, z,w = acceleration
#if defined(DEFORM)
layout(location = 0) in vec4 vPosRot; //x = x, y = y, z = radians of rotation, w = rotation velocity
#else
layout(location = 0) in vec2 vPos;
#endif
#if defined(COLOR)
layout(location = 3) in vec4 vColor;
#endif
#if !defined(DEFORM) && !defined(POINT) // Color-level
layout(location = 4) in vec2 vTex; // x = tx, y = ty
#endif
#if defined(DEFORM)
layout(location = 4) in vec4 vDeformVec; // x,y x unit vector; z,w = y unit vector
layout(location = 5) in vec3 vTex; // x = tx, y = ty, z = bool autoRotate
#endif
#if defined(SPRITE)
layout(location = 6) in vec3 vAnimData; // w,h(premultiplied of anim), interpolation progress
layout(location = 7) in vec3 vAnimPos; // x, y, x2 (two frames for interpolation)
#endif
#if defined(TABLE)
layout(location = 0) out vec2 tt; //y is progress if Sprite mode
#endif
#if defined(SPRITE)
layout(location = 1) out vec4 fTexS;
#elif !defined(POINT)
layout(location = 1) out vec2 fTex;
#endif
#if defined(COLOR)
layout(location = 2) out vec4 fColor;
#else
layout(location = 2) out float fFade;
#endif
layout(std140, binding = 0) uniform buf {
#if QSHADER_VIEW_COUNT >= 2
mat4 matrix[QSHADER_VIEW_COUNT];
#else
mat4 matrix;
#endif
float opacity;
float entry;
float timestamp;
float dpr;
float sizetable[64];
float opacitytable[64];
} ubuf;
void main()
{
float t = (ubuf.timestamp - vData.x) / vData.y;
#if QSHADER_VIEW_COUNT >= 2
mat4 matrix = ubuf.matrix[gl_ViewIndex];
#else
mat4 matrix = ubuf.matrix;
#endif
if (t < 0. || t > 1.) {
#if defined(DEFORM)
gl_Position = matrix * vec4(vPosRot.x, vPosRot.y, 0., 1.);
#elif defined(POINT)
gl_PointSize = 0.;
#else
gl_Position = matrix * vec4(vPos.x, vPos.y, 0., 1.);
#endif
} else {
#if defined(SPRITE)
tt.y = vAnimData.z;
// Calculate frame location in texture
fTexS.xy = vAnimPos.xy + vTex.xy * vAnimData.xy;
// Next frame is also passed, for interpolation
fTexS.zw = vAnimPos.zy + vTex.xy * vAnimData.xy;
#elif !defined(POINT)
fTex = vTex.xy;
#endif
float currentSize = mix(vData.z, vData.w, t * t);
float fade = 1.;
float fadeIn = min(t * 10., 1.);
float fadeOut = 1. - clamp((t - 0.75) * 4.,0., 1.);
#if defined(TABLE)
currentSize = currentSize * ubuf.sizetable[int(floor(t*64.))];
fade = fade * ubuf.opacitytable[int(floor(t*64.))];
#endif
if (ubuf.entry == 1.)
fade = fade * fadeIn * fadeOut;
else if (ubuf.entry == 2.)
currentSize = currentSize * fadeIn * fadeOut;
if (currentSize <= 0.) {
#if defined(DEFORM)
gl_Position = matrix * vec4(vPosRot.x, vPosRot.y, 0., 1.);
#elif defined(POINT)
gl_PointSize = 0.;
#else
gl_Position = matrix * vec4(vPos.x, vPos.y, 0., 1.);
#endif
} else {
if (currentSize < 3.) // Sizes too small look jittery as they move
currentSize = 3.;
vec2 pos;
#if defined(DEFORM)
float rotation = vPosRot.z + vPosRot.w * t * vData.y;
if (vTex.z > 0.) {
vec2 curVel = vVec.zw * t * vData.y + vVec.xy;
if (length(curVel) > 0.)
rotation += atan(curVel.y, curVel.x);
}
vec2 trigCalcs = vec2(cos(rotation), sin(rotation));
vec4 deform = vDeformVec * currentSize * (vTex.xxyy - 0.5);
vec4 rotatedDeform = deform.xxzz * trigCalcs.xyxy;
rotatedDeform = rotatedDeform + (deform.yyww * trigCalcs.yxyx * vec4(-1.,1.,-1.,1.));
/* The readable version:
vec2 xDeform = vDeformVec.xy * currentSize * (vTex.x-0.5);
vec2 yDeform = vDeformVec.zw * currentSize * (vTex.y-0.5);
vec2 xRotatedDeform;
xRotatedDeform.x = trigCalcs.x*xDeform.x - trigCalcs.y*xDeform.y;
xRotatedDeform.y = trigCalcs.y*xDeform.x + trigCalcs.x*xDeform.y;
vec2 yRotatedDeform;
yRotatedDeform.x = trigCalcs.x*yDeform.x - trigCalcs.y*yDeform.y;
yRotatedDeform.y = trigCalcs.y*yDeform.x + trigCalcs.x*yDeform.y;
*/
pos = vPosRot.xy
+ rotatedDeform.xy
+ rotatedDeform.zw
+ vVec.xy * t * vData.y // apply velocity
+ 0.5 * vVec.zw * pow(t * vData.y, 2.); // apply acceleration
#elif defined(POINT)
pos = vPos.xy
+ vVec.xy * t * vData.y // apply velocity vector..
+ 0.5 * vVec.zw * pow(t * vData.y, 2.);
gl_PointSize = currentSize * ubuf.dpr;
#else // non point color
vec2 deform = currentSize * (vTex.xy - 0.5);
pos = vPos.xy
+ deform.xy
+ vVec.xy * t * vData.y // apply velocity
+ 0.5 * vVec.zw * pow(t * vData.y, 2.); // apply acceleration
#endif
gl_Position = matrix * vec4(pos.x, pos.y, 0, 1);
#if defined(COLOR)
fColor = vColor * fade;
#else
fFade = fade;
#endif
#if defined(TABLE)
tt.x = t;
#endif
}
}
}