/**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the Declarative module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qsgparticlesystem_p.h" #include #include "qsgparticleemitter_p.h" #include "qsgparticleaffector_p.h" #include "qsgparticlepainter_p.h" #include "qsgspriteengine_p.h" #include "qsgsprite_p.h" #include "qsgfollowemitter_p.h"//###For auto-follow on states, perhaps should be in emitter? #include #include QT_BEGIN_NAMESPACE const qreal EPSILON = 0.001; //Utility functions for when within 1ms is close enough bool timeEqualOrGreater(qreal a, qreal b){ return (a+EPSILON >= b); } bool timeLess(qreal a, qreal b){ return (a-EPSILON < b); } bool timeEqual(qreal a, qreal b){ return (a+EPSILON > b) && (a-EPSILON < b); } int roundedTime(qreal a){// in ms return (int)qRound(a*1000.0); } QSGParticleDataHeap::QSGParticleDataHeap() : m_data(0) { m_data.reserve(1000); clear(); } void QSGParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData { m_data.resize(1 << ++m_size); } void QSGParticleDataHeap::insert(QSGParticleData* data) { int time = roundedTime(data->t + data->lifeSpan); 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 QSGParticleDataHeap::top() { if (m_end == 0) return 1e24; return m_data[0].time; } QSet QSGParticleDataHeap::pop() { if (!m_end) return QSet (); QSet 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 QSGParticleDataHeap::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 QSGParticleDataHeap::contains(QSGParticleData* d) { for (int i=0; i= 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); } } QSGParticleGroupData::QSGParticleGroupData(int id, QSGParticleSystem* sys):index(id),m_size(0),m_system(sys) { initList(); } QSGParticleGroupData::~QSGParticleGroupData() { foreach (QSGParticleData* d, data) delete d; } int QSGParticleGroupData::size() { return m_size; } QString QSGParticleGroupData::name()//### Worth caching as well? { return m_system->m_groupIds.key(index); } void QSGParticleGroupData::setSize(int newSize){ if (newSize == m_size) return; Q_ASSERT(newSize > m_size);//XXX allow shrinking data.resize(newSize); for (int i=m_size; igroup = index; data[i]->index = i; reusableIndexes << i; } int delta = newSize - m_size; m_size = newSize; foreach (QSGParticlePainter* p, painters) p->setCount(p->count() + delta); } void QSGParticleGroupData::initList() { dataHeap.clear(); } void QSGParticleGroupData::kill(QSGParticleData* d){ Q_ASSERT(d->group == index); d->lifeSpan = 0;//Kill off foreach (QSGParticlePainter* p, painters) p->reload(d); reusableIndexes << d->index; } QSGParticleData* QSGParticleGroupData::newDatum(bool respectsLimits){ while (dataHeap.top() <= m_system->m_timeInt){ foreach (QSGParticleData* datum, dataHeap.pop()){ if (!datum->stillAlive()){ reusableIndexes << datum->index; }else{ prepareRecycler(datum); //ttl has been altered mid-way, put it back } } } while (!reusableIndexes.empty()){ int idx = *(reusableIndexes.begin()); reusableIndexes.remove(idx); if (data[idx]->stillAlive()){// ### This means resurrection of dead particles. Is that allowed? prepareRecycler(data[idx]); continue; } return data[idx]; } if (respectsLimits) return 0; int oldSize = m_size; setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily reusableIndexes.remove(oldSize); return data[oldSize]; } void QSGParticleGroupData::prepareRecycler(QSGParticleData* d){ dataHeap.insert(d); } QSGParticleData::QSGParticleData(QSGParticleSystem* sys) : group(0) , e(0) , system(sys) , index(0) , systemIndex(-1) { x = 0; y = 0; t = -1; lifeSpan = 0; size = 0; endSize = 0; sx = 0; sy = 0; ax = 0; ay = 0; xx = 1; xy = 0; yx = 0; yy = 1; rotation = 0; rotationSpeed = 0; autoRotate = 0; animIdx = 0; frameDuration = 1; frameCount = 1; animT = -1; color.r = 255; color.g = 255; color.b = 255; color.a = 255; r = 0; delegate = 0; modelIndex = -1; } void QSGParticleData::clone(const QSGParticleData& other) { x = other.x; y = other.y; t = other.t; lifeSpan = other.lifeSpan; size = other.size; endSize = other.endSize; sx = other.sx; sy = other.sy; ax = other.ax; ay = other.ay; xx = other.xx; xy = other.xy; yx = other.yx; yy = other.yy; rotation = other.rotation; rotationSpeed = other.rotationSpeed; autoRotate = other.autoRotate; animIdx = other.animIdx; frameDuration = other.frameDuration; frameCount = other.frameCount; animT = other.animT; color.r = other.color.r; color.g = other.color.g; color.b = other.color.b; color.a = other.color.a; r = other.r; delegate = other.delegate; modelIndex = other.modelIndex; } //sets the x accleration without affecting the instantaneous x velocity or position void QSGParticleData::setInstantaneousAX(qreal ax) { qreal t = (system->m_timeInt / 1000.0) - this->t; qreal sx = (this->sx + t*this->ax) - t*ax; qreal ex = this->x + this->sx * t + 0.5 * this->ax * t * t; qreal x = ex - t*sx - 0.5 * t*t*ax; this->ax = ax; this->sx = sx; this->x = x; } //sets the x velocity without affecting the instantaneous x postion void QSGParticleData::setInstantaneousSX(qreal vx) { qreal t = (system->m_timeInt / 1000.0) - this->t; qreal sx = vx - t*this->ax; qreal ex = this->x + this->sx * t + 0.5 * this->ax * t * t; qreal x = ex - t*sx - 0.5 * t*t*this->ax; this->sx = sx; this->x = x; } //sets the instantaneous x postion void QSGParticleData::setInstantaneousX(qreal x) { qreal t = (system->m_timeInt / 1000.0) - this->t; this->x = x - t*this->sx - 0.5 * t*t*this->ax; } //sets the y accleration without affecting the instantaneous y velocity or position void QSGParticleData::setInstantaneousAY(qreal ay) { qreal t = (system->m_timeInt / 1000.0) - this->t; qreal sy = (this->sy + t*this->ay) - t*ay; qreal ey = this->y + this->sy * t + 0.5 * this->ay * t * t; qreal y = ey - t*sy - 0.5 * t*t*ay; this->ay = ay; this->sy = sy; this->y = y; } //sets the y velocity without affecting the instantaneous y position void QSGParticleData::setInstantaneousSY(qreal vy) { qreal t = (system->m_timeInt / 1000.0) - this->t; qreal sy = vy - t*this->ay; qreal ey = this->y + this->sy * t + 0.5 * this->ay * t * t; qreal y = ey - t*sy - 0.5 * t*t*this->ay; this->sy = sy; this->y = y; } //sets the instantaneous Y position void QSGParticleData::setInstantaneousY(qreal y) { qreal t = (system->m_timeInt / 1000.0) - this->t; this->y = y - t*this->sy - 0.5 * t*t*this->ay; } qreal QSGParticleData::curX() const { qreal t = (system->m_timeInt / 1000.0) - this->t; return this->x + this->sx * t + 0.5 * this->ax * t * t; } qreal QSGParticleData::curSX() const { qreal t = (system->m_timeInt / 1000.0) - this->t; return this->sx + t*this->ax; } qreal QSGParticleData::curY() const { qreal t = (system->m_timeInt / 1000.0) - this->t; return y + sy * t + 0.5 * ay * t * t; } qreal QSGParticleData::curSY() const { qreal t = (system->m_timeInt / 1000.0) - this->t; return sy + t*ay; } void QSGParticleData::debugDump() { qDebug() << "Particle" << systemIndex << group << "/" << index << stillAlive() << "Pos: " << x << "," << y //<< "Vel: " << sx << "," << sy //<< "Acc: " << ax << "," << ay << "Size: " << size << "," << endSize << "Time: " << t << "," <m_timeInt / 1000.0) ; } bool QSGParticleData::stillAlive() { if (!system) return false; //fprintf(stderr, "%.9lf %.9lf\n",((qreal)system->m_timeInt/1000.0), (t+lifeSpan)); return (t + lifeSpan - EPSILON) > ((qreal)system->m_timeInt/1000.0); } float QSGParticleData::curSize() { if (!system || !lifeSpan) return 0.0f; return size + (endSize - size) * (lifeLeft() / lifeSpan); } float QSGParticleData::lifeLeft() { if (!system) return 0.0f; return (t + lifeSpan) - (system->m_timeInt/1000.0); } QSGParticleSystem::QSGParticleSystem(QSGItem *parent) : QSGItem(parent), m_particle_count(0), m_running(true) , m_startTime(0), m_nextIndex(0), m_componentComplete(false), m_spriteEngine(0) { QSGParticleGroupData* gd = new QSGParticleGroupData(0, this);//Default group m_groupData.insert(0,gd); m_groupIds.insert("",0); m_nextGroupId = 1; connect(&m_painterMapper, SIGNAL(mapped(QObject*)), this, SLOT(loadPainter(QObject*))); } QSGParticleSystem::~QSGParticleSystem() { foreach (QSGParticleGroupData* gd, m_groupData) delete gd; } QDeclarativeListProperty QSGParticleSystem::particleStates() { return QDeclarativeListProperty(this, &m_states, spriteAppend, spriteCount, spriteAt, spriteClear); } void QSGParticleSystem::registerParticlePainter(QSGParticlePainter* p) { //TODO: a way to Unregister emitters, painters and affectors m_particlePainters << QPointer(p);//###Set or uniqueness checking? connect(p, SIGNAL(particlesChanged(QStringList)), &m_painterMapper, SLOT(map())); loadPainter(p); p->update();//###Initial update here? } void QSGParticleSystem::registerParticleEmitter(QSGParticleEmitter* e) { m_emitters << QPointer(e);//###How to get them out? connect(e, SIGNAL(particleCountChanged()), this, SLOT(emittersChanged())); connect(e, SIGNAL(particleChanged(QString)), this, SLOT(emittersChanged())); emittersChanged(); e->reset();//Start, so that starttime factors appropriately } void QSGParticleSystem::registerParticleAffector(QSGParticleAffector* a) { m_affectors << QPointer(a); } void QSGParticleSystem::loadPainter(QObject *p) { if (!m_componentComplete) return; QSGParticlePainter* painter = qobject_cast(p); Q_ASSERT(painter);//XXX foreach (QSGParticleGroupData* sg, m_groupData) sg->painters.remove(painter); int particleCount = 0; if (painter->particles().isEmpty()){//Uses default particle QStringList def; def << ""; painter->setParticles(def); particleCount += m_groupData[0]->size(); m_groupData[0]->painters << painter; }else{ foreach (const QString &group, painter->particles()){ if (group != QLatin1String("") && !m_groupIds[group]){//new group int id = m_nextGroupId++; QSGParticleGroupData* gd = new QSGParticleGroupData(id, this); m_groupIds.insert(group, id); m_groupData.insert(id, gd); } particleCount += m_groupData[m_groupIds[group]]->size(); m_groupData[m_groupIds[group]]->painters << painter; } } painter->setCount(particleCount); painter->update();//###Initial update here? return; } void QSGParticleSystem::emittersChanged() { if (!m_componentComplete) return; m_emitters.removeAll(0); QList previousSizes; QList newSizes; for (int i=0; isize(); newSizes << 0; } foreach (QSGParticleEmitter* e, m_emitters){//Populate groups and set sizes. if (!m_groupIds.contains(e->particle()) || (!e->particle().isEmpty() && !m_groupIds[e->particle()])){//or it was accidentally inserted by a failed lookup earlier int id = m_nextGroupId++; QSGParticleGroupData* gd = new QSGParticleGroupData(id, this); m_groupIds.insert(e->particle(), id); m_groupData.insert(id, gd); previousSizes << 0; newSizes << 0; } newSizes[m_groupIds[e->particle()]] += e->particleCount(); //###: Cull emptied groups? } //TODO: Garbage collection? m_particle_count = 0; for (int i=0; isetSize(qMax(newSizes[i], previousSizes[i])); m_particle_count += m_groupData[i]->size(); } Q_ASSERT(m_particle_count >= m_bySysIdx.size());//XXX when GC done right m_bySysIdx.resize(m_particle_count); foreach (QSGParticlePainter *p, m_particlePainters) loadPainter(p); if (!m_states.isEmpty()) createEngine(); if (m_particle_count > 16000)//###Investigate if these limits are worth warning about? qWarning() << "Particle system arbitarily believes it has a vast number of particles (>16000). Expect poor performance"; } void QSGParticleSystem::setRunning(bool arg) { if (m_running != arg) { m_running = arg; emit runningChanged(arg); reset(); } } void QSGParticleSystem::stateRedirect(QDeclarativeListProperty *prop, QObject *value) { //Hooks up automatic state-associated stuff QSGParticleSystem* sys = qobject_cast(prop->object->parent()); QSGSprite* sprite = qobject_cast(prop->object); if (!sprite || !sys) return; QStringList list; list << sprite->name(); QSGParticleAffector* a = qobject_cast(value); if (a){ a->setParentItem(sys); a->setParticles(list); a->setSystem(sys); return; } QSGFollowEmitter* e = qobject_cast(value); if (e){ e->setParentItem(sys); e->setFollow(sprite->name()); e->setSystem(sys); return; } QSGParticlePainter* p = qobject_cast(value); if (p){ p->setParentItem(sys); p->setParticles(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."; } void QSGParticleSystem::componentComplete() { QSGItem::componentComplete(); m_componentComplete = true; //if (!m_emitters.isEmpty() && !m_particlePainters.isEmpty()) reset(); } void QSGParticleSystem::reset()//TODO: Needed? Or just in component complete? { if (!m_componentComplete) return; //Clear guarded pointers which have been deleted int cleared = 0; cleared += m_emitters.removeAll(0); cleared += m_particlePainters.removeAll(0); cleared += m_affectors.removeAll(0); emittersChanged(); //TODO: Reset data // foreach (QSGParticlePainter* p, m_particlePainters) // p->reset(); // foreach (QSGParticleEmitter* e, m_emitters) // e->reset(); //### Do affectors need reset too? if (!m_running) return; foreach (QSGParticlePainter *p, m_particlePainters){ loadPainter(p); p->reset(); } m_timeInt = 0; m_timestamp.restart();//TODO: Better placement m_initialized = true; } void QSGParticleSystem::createEngine() { if (!m_componentComplete) return; //### Solve the losses if size/states go down foreach (QSGSprite* sprite, m_states){ bool exists = false; foreach (const QString &name, m_groupIds.keys()) if (sprite->name() == name) exists = true; if (!exists){ int id = m_nextGroupId++; QSGParticleGroupData* gd = new QSGParticleGroupData(id, this); m_groupIds.insert(sprite->name(), id); m_groupData.insert(id, gd); } } if (m_states.count()){ //Reorder Sprite List so as to have the same order as groups QList newList; for (int i=0; iname(); foreach (QSGSprite* existing, m_states){ if (existing->name() == name){ newList << existing; exists = true; } } if (!exists){ newList << new QSGSprite(this); newList.back()->setName(name); } } m_states = newList; if (!m_spriteEngine) m_spriteEngine = new QSGSpriteEngine(this); m_spriteEngine->setCount(m_particle_count); m_spriteEngine->m_states = m_states; connect(m_spriteEngine, SIGNAL(stateChanged(int)), this, SLOT(particleStateChange(int))); }else{ if (m_spriteEngine) delete m_spriteEngine; m_spriteEngine = 0; } } void QSGParticleSystem::particleStateChange(int idx) { moveGroups(m_bySysIdx[idx], m_spriteEngine->spriteState(idx)); } void QSGParticleSystem::moveGroups(QSGParticleData *d, int newGIdx) { QSGParticleData* pd = newDatum(newGIdx, false, d->systemIndex); pd->clone(*d); finishNewDatum(pd); d->systemIndex = -1; m_groupData[d->group]->kill(d); } int QSGParticleSystem::nextSystemIndex() { if (!m_reusableIndexes.isEmpty()){ int ret = *(m_reusableIndexes.begin()); m_reusableIndexes.remove(ret); return ret; } if (m_nextIndex >= m_bySysIdx.size()){ m_bySysIdx.resize(m_bySysIdx.size() < 10 ? 10 : m_bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily if (m_spriteEngine) m_spriteEngine->setCount(m_bySysIdx.size()); } return m_nextIndex++; } QSGParticleData* QSGParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex) { Q_ASSERT(groupId < m_groupData.count());//XXX shouldn't really be an assert QSGParticleData* ret = m_groupData[groupId]->newDatum(respectLimits); if (!ret){ return 0; } if (sysIndex == -1){ if (ret->systemIndex == -1) ret->systemIndex = nextSystemIndex(); }else{ if (ret->systemIndex != -1){ if (m_spriteEngine) m_spriteEngine->stopSprite(ret->systemIndex); m_reusableIndexes << ret->systemIndex; m_bySysIdx[ret->systemIndex] = 0; } ret->systemIndex = sysIndex; } m_bySysIdx[ret->systemIndex] = ret; if (m_spriteEngine) m_spriteEngine->startSprite(ret->systemIndex, ret->group); return ret; } void QSGParticleSystem::emitParticle(QSGParticleData* pd) {// called from prepareNextFrame()->emitWindow - enforce? //Account for relative emitter position QPointF offset = this->mapFromItem(pd->e, QPointF(0, 0)); if (!offset.isNull()){ pd->x += offset.x(); pd->y += offset.y(); } finishNewDatum(pd); } void QSGParticleSystem::finishNewDatum(QSGParticleData *pd){ m_groupData[pd->group]->prepareRecycler(pd); foreach (QSGParticleAffector *a, m_affectors) if (a && a->m_needsReset) a->reset(pd); foreach (QSGParticlePainter* p, m_groupData[pd->group]->painters) if (p) p->load(pd); } qint64 QSGParticleSystem::systemSync(QSGParticlePainter* p) { if (!m_running) return 0; if (!m_initialized) return 0;//error in initialization if (m_syncList.isEmpty() || m_syncList.contains(p)){//Need to advance the simulation m_syncList.clear(); //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time. qreal dt = m_timeInt / 1000.; m_timeInt = m_timestamp.elapsed() + m_startTime; qreal time = m_timeInt / 1000.; dt = time - dt; m_needsReset.clear(); if (m_spriteEngine) m_spriteEngine->updateSprites(m_timeInt); foreach (QSGParticleEmitter* emitter, m_emitters) if (emitter) emitter->emitWindow(m_timeInt); foreach (QSGParticleAffector* a, m_affectors) if (a) a->affectSystem(dt); foreach (QSGParticleData* d, m_needsReset) foreach (QSGParticlePainter* p, m_groupData[d->group]->painters) if (p && d) p->reload(d); } m_syncList << p; return m_timeInt; } QT_END_NAMESPACE