Implement Turbulence properly
authorAlan Alpert <alan.alpert@nokia.com>
Fri, 9 Sep 2011 07:21:14 +0000 (17:21 +1000)
committerQt by Nokia <qt-info@nokia.com>
Tue, 13 Sep 2011 01:18:45 +0000 (03:18 +0200)
Or at least closer to. Now uses curl noise off of a source noise image,
documented as usable, and not an immense performance drain (just a
normal one).

Change-Id: Iac11c98cd9589cbe6a41b2b30893ab40d541d18f
Reviewed-on: http://codereview.qt-project.org/4510
Reviewed-by: Alan Alpert <alan.alpert@nokia.com>

examples/declarative/flickr/content/ImageDetails.qml
examples/declarative/particles/trails/turbulence.qml
src/declarative/particles/defaultshaders/noise.png [new file with mode: 0644]
src/declarative/particles/particles.qrc
src/declarative/particles/qsgturbulence.cpp
src/declarative/particles/qsgturbulence_p.h

index 8d3cdfb..3d64a33 100644 (file)
@@ -162,8 +162,7 @@ Flipable {
                     id: turbulence
                     system: imageSystem
                     anchors.fill: parent 
-                    frequency: 100
-                    strength: 250
+                    strength: 240
                     enabled: false
                 }
 
index 62216c3..104bb10 100644 (file)
@@ -56,15 +56,19 @@ Rectangle{
     ParticleSystem{
         id: ps
     }
+    MouseArea{
+        anchors.fill: parent
+        onClicked: turb.enabled = !turb.enabled
+    }
     Turbulence{
+        id: turb
         system: ps
+        enabled: true
         height: (parent.height / 2)
         width: parent.width / 2
         x: parent. width / 4
         anchors.fill: parent
-        strength: 16
-        frequency: 64
-        gridSize: 16
+        strength: 32
     }
     ImageParticle{
         particles: ["smoke"]
@@ -79,7 +83,7 @@ Rectangle{
         source: "content/particle.png"
         color: "#11ff400f"
         colorVariation: 0.1
-        }
+    }
     Emitter{
         anchors.centerIn: parent
         system: ps
@@ -96,12 +100,12 @@ Rectangle{
     TrailEmitter{
         id: smoke1
         width: root.width
-        height: 258
+        height: root.height/2 - 20
         system: ps
         particle: "smoke"
         follow: "flame"
 
-        emitRatePerParticle: 4
+        emitRatePerParticle: 1
         lifeSpan: 2400
         lifeSpanVariation: 400
         size: 16
@@ -113,16 +117,16 @@ Rectangle{
     TrailEmitter{
         id: smoke2
         width: root.width
-        height: 232
+        height: root.height/2 - 40
         system: ps
         particle: "smoke"
         follow: "flame"
         
-        emitRatePerParticle: 1
+        emitRatePerParticle: 4
         lifeSpan: 2400
         size: 36
         endSize: 24
-        sizeVariation: 8
+        sizeVariation: 12
         acceleration: PointDirection{ y: -40 }
         speed: AngleDirection{ angle: 270; magnitude: 40; angleVariation: 22; magnitudeVariation: 5 }
     }
diff --git a/src/declarative/particles/defaultshaders/noise.png b/src/declarative/particles/defaultshaders/noise.png
new file mode 100644 (file)
index 0000000..3c723e1
Binary files /dev/null and b/src/declarative/particles/defaultshaders/noise.png differ
index 5403f55..db00a57 100644 (file)
@@ -6,5 +6,6 @@
         <file>defaultshaders/spriteimagevertex.shader</file>
         <file>defaultshaders/identitytable.png</file>
         <file>defaultshaders/defaultFadeInOut.png</file>
+        <file>defaultshaders/noise.png</file>
     </qresource>
 </RCC>
index 2caebc7..c24af1b 100644 (file)
@@ -50,27 +50,43 @@ QT_BEGIN_NAMESPACE
     \qmlclass Turbulence QSGTurbulenceAffector
     \inqmlmodule QtQuick.Particles 2
     \inherits Affector
-    \brief The TurbulenceAffector is a bit of a hack and probably shouldn't be used yet.
+    \brief Turbulence provides fluid like forces based on 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 int QtQuick.Particles2::Turbulence::strength
-    Maximum magnitude of a point in the vector field.
-*/
-/*!
-    \qmlproperty int QtQuick.Particles2::Turbulence::frequency
-    Times per second vector field is perturbed.
+    \qmlproperty real QtQuick.Particles2::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 int QtQuick.Particles2::Turbulence::gridSize
-    Square root of the number of points in the vector field simulcrum.
+    \qmlproperty url QtQuick.Particles2::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.
 */
 
 QSGTurbulenceAffector::QSGTurbulenceAffector(QSGItem *parent) :
     QSGParticleAffector(parent),
-    m_strength(10), m_lastT(0), m_frequency(64), m_gridSize(10), m_field(0), m_inited(false)
+    m_strength(10), m_lastT(0), m_gridSize(0), m_field(0), m_vectorField(0), m_inited(false)
+{
+}
+
+void QSGTurbulenceAffector::geometryChanged(const QRectF &, const QRectF &)
 {
-    //TODO: Update grid on size change
+    initializeGrid();
 }
 
 QSGTurbulenceAffector::~QSGTurbulenceAffector()
@@ -80,6 +96,11 @@ QSGTurbulenceAffector::~QSGTurbulenceAffector()
             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);
+    }
 }
 
 static qreal magnitude(qreal x, qreal y)
@@ -87,8 +108,12 @@ static qreal magnitude(qreal x, qreal y)
     return sqrt(x*x + y*y);
 }
 
-void QSGTurbulenceAffector::setSize(int arg)
+void QSGTurbulenceAffector::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++)
@@ -96,81 +121,77 @@ void QSGTurbulenceAffector::setSize(int arg)
             free(m_field);
             m_system = 0;
         }
+        if (m_vectorField) {
+            for (int i=0; i<m_gridSize; i++)
+                free(m_vectorField[i]);
+            free(m_vectorField);
+        }
         m_gridSize = arg;
-        emit sizeChanged(arg);
     }
-}
 
-void QSGTurbulenceAffector::ensureInit()
-{
-    if (m_inited)
-        return;
-    m_inited = true;
-    m_field = (QPointF**)malloc(m_gridSize * sizeof(QPointF*));
+    m_field = (qreal**)malloc(m_gridSize * sizeof(qreal*));
     for (int i=0; i<m_gridSize; i++)
-        m_field[i]  = (QPointF*)malloc(m_gridSize * sizeof(QPointF));
+        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 = QImage(m_noiseSource.toLocalFile()).scaled(QSize(m_gridSize, m_gridSize));
+    if (image.isNull())
+        image = QImage(":defaultshaders/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] = QPointF();
-    m_spacing = QPointF(width()/m_gridSize, height()/m_gridSize);
-    m_magSum = magnitude(m_spacing.x(), m_spacing.y())*2;
+            m_field[i][j] = qRed(image.pixel(QPoint(i,j)));//Red as proxy for Value
+    for (int i=0; i<m_gridSize; i++){
+        for (int j=0; j<m_gridSize; j++){
+            m_vectorField[i][j].setX(boundsRespectingField(i,j) - boundsRespectingField(i,j-1));
+            m_vectorField[i][j].setY(boundsRespectingField(i-1,j) - boundsRespectingField(i,j));
+        }
+    }
 }
 
-void QSGTurbulenceAffector::mapUpdate()
+qreal QSGTurbulenceAffector::boundsRespectingField(int x, int y)
 {
-    QPoint pos(rand() % m_gridSize, rand() % m_gridSize);
-    QPointF vector(m_strength  - (((qreal)rand() / RAND_MAX) * m_strength*2),
-                   m_strength  - (((qreal)rand() / RAND_MAX) * m_strength*2));
-    for (int i = 0; i < m_gridSize; i++){
-        for (int j = 0; j < m_gridSize; j++){
-            qreal dist = magnitude(i-pos.x(), j-pos.y());
-            m_field[i][j] += vector/(dist + 1);
-            if (magnitude(m_field[i][j].x(), m_field[i][j].y()) > m_strength){
-                //Speed limit
-                qreal theta = atan2(m_field[i][j].y(), m_field[i][j].x());
-                m_field[i][j].setX(m_strength * cos(theta));
-                m_field[i][j].setY(m_strength * sin(theta));
-            }
-        }
-    }
+    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 QSGTurbulenceAffector::ensureInit()
+{
+    if (m_inited)
+        return;
+    m_inited = true;
+    initializeGrid();
+}
 
 void QSGTurbulenceAffector::affectSystem(qreal dt)
 {
     if (!m_system || !m_enabled)
         return;
     ensureInit();
-    qreal period = 1.0/m_frequency;
-    qreal time = m_system->m_timeInt / 1000.0;
-    while ( m_lastT < time ){
-        mapUpdate();
-        m_lastT += period;
-    }
 
+    QRectF boundsRect(0, 0, width()-1, height()-1);
     foreach (QSGParticleGroupData *gd, m_system->m_groupData){
         if (!activeGroup(m_system->m_groupData.key(gd)))//TODO: Surely this can be done better
-            return;
+            continue;
         foreach (QSGParticleData *d, gd->data){
-            if (!d || !activeGroup(d->group))
-                return;
+            if (!d || !activeGroup(d->group) || !d->stillAlive())
+                continue;
+            QPoint pos = (QPointF(d->curX(), d->curY()) - m_offset).toPoint();
+            if (!boundsRect.contains(pos))
+                continue;
             qreal fx = 0.0;
             qreal fy = 0.0;
-            QPointF pos = QPointF(d->curX() - x(), d->curY() - y());//TODO: Offset
-            QPointF nodePos = QPointF(pos.x() / m_spacing.x(), pos.y() / m_spacing.y());
-            QSet<QPair<int, int> > nodes;
-            nodes << qMakePair((int)ceil(nodePos.x()), (int)ceil(nodePos.y()));
-            nodes << qMakePair((int)ceil(nodePos.x()), (int)floor(nodePos.y()));
-            nodes << qMakePair((int)floor(nodePos.x()), (int)ceil(nodePos.y()));
-            nodes << qMakePair((int)floor(nodePos.x()), (int)floor(nodePos.y()));
-            typedef QPair<int, int> intPair;
-            foreach (const intPair &p, nodes){
-                if (!QRect(0,0,m_gridSize-1,m_gridSize-1).contains(QPoint(p.first, p.second)))
-                    continue;
-                qreal dist = magnitude(pos.x() - p.first*m_spacing.x(), pos.y() - p.second*m_spacing.y());//TODO: Mathematically valid
-                fx += m_field[p.first][p.second].x() * ((m_magSum - dist)/m_magSum);//Proportionally weight nodes
-                fy += m_field[p.first][p.second].y() * ((m_magSum - dist)/m_magSum);
-            }
+            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()+ fx * dt);
                 d->setInstantaneousVY(d->curVY()+ fy * dt);
index dd938f1..110ecf4 100644 (file)
@@ -56,68 +56,61 @@ class QSGParticlePainter;
 class QSGTurbulenceAffector : public QSGParticleAffector
 {
     Q_OBJECT
-    Q_PROPERTY(int strength READ strength WRITE setStrength NOTIFY strengthChanged)
-    Q_PROPERTY(int frequency READ frequency WRITE setFrequency NOTIFY frequencyChanged)
-    Q_PROPERTY(int gridSize READ size WRITE setSize NOTIFY sizeChanged)
-public:
+    Q_PROPERTY(qreal strength READ strength WRITE setStrength NOTIFY strengthChanged)
+    Q_PROPERTY(QUrl noiseSource READ noiseSource WRITE setNoiseSource NOTIFY noiseSourceChanged)
+    public:
     explicit QSGTurbulenceAffector(QSGItem *parent = 0);
     ~QSGTurbulenceAffector();
     virtual void affectSystem(qreal dt);
 
-    int strength() const
+    qreal strength() const
     {
         return m_strength;
     }
 
-    int frequency() const
+    QUrl noiseSource() const
     {
-        return m_frequency;
+        return m_noiseSource;
     }
-
-    int size() const
-    {
-        return m_gridSize;
-    }
-
 signals:
 
-    void strengthChanged(int arg);
-
-    void frequencyChanged(int arg);
+    void strengthChanged(qreal arg);
 
-    void sizeChanged(int arg);
+    void noiseSourceChanged(QUrl arg);
 
 public slots:
+        void initializeGrid();
 
-void setStrength(int arg)
-{
-    if (m_strength != arg) {
-        m_strength = arg;
-        emit strengthChanged(arg);
+    void setStrength(qreal arg)
+    {
+        if (m_strength != arg) {
+            m_strength = arg;
+            emit strengthChanged(arg);
+        }
     }
-}
 
-void setFrequency(int arg)
-{
-    if (m_frequency != arg) {
-        m_frequency = arg;
-        emit frequencyChanged(arg);
+    void setNoiseSource(QUrl arg)
+    {
+        if (m_noiseSource != arg) {
+            m_noiseSource = arg;
+            emit noiseSourceChanged(arg);
+        }
     }
-}
-
-void setSize(int arg);
 
+protected:
+    virtual void geometryChanged(const QRectF &newGeometry,
+                                 const QRectF &oldGeometry);
 private:
     void ensureInit();
     void mapUpdate();
-    int m_strength;
+    qreal boundsRespectingField(int x, int y);
+    qreal m_strength;
     qreal m_lastT;
-    int m_frequency;
     int m_gridSize;
-    QPointF** m_field;
-    QPointF m_spacing;
-    qreal m_magSum;
+    qreal** m_field;
+    QPointF** m_vectorField;
     bool m_inited;
+    QUrl m_noiseSource;
 };
 
 QT_END_NAMESPACE