From b3af5152240c20583f93855906b0beac66d50c35 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Mon, 11 May 2015 19:21:33 -0700 Subject: [PATCH] Particle entity improvements based on code review. * Updated variable naming to match coding standards. * Changed particle raw float arrays to vectors. * Bug fix: changing maxParticles will no longer lead to memory corruption. * Made particle ring buffer more explicit, added _particleTailIndex. * Added getLivingParticleCount() private method. * Moved integration and bounds code into private methods. * Bug fix: high emit rates now properly integrate particles forward for the remaing frame time, not the entire frame. * Bug fix: new particles were not initiaized to origin. * Added more particles to ajt-test.js. * Bug fix: ajt-test.js script was not shutting down properly. * Removed random seed, unless we have a psudo random number generator per particle system instance, it's unlikely that this will preserve sync. * Bumped packet version number. --- examples/ajt-test.js | 34 ++- .../RenderableParticleEffectEntityItem.cpp | 143 +++++------ .../entities/src/ParticleEffectEntityItem.cpp | 232 +++++++++--------- .../entities/src/ParticleEffectEntityItem.h | 51 ++-- libraries/networking/src/PacketHeaders.cpp | 2 +- libraries/networking/src/PacketHeaders.h | 1 + 6 files changed, 238 insertions(+), 225 deletions(-) diff --git a/examples/ajt-test.js b/examples/ajt-test.js index a908978a02..c26458a9af 100644 --- a/examples/ajt-test.js +++ b/examples/ajt-test.js @@ -28,32 +28,34 @@ } // constructor - function TestFx() { + function TestFx(color, emitDirection, emitRate, emitStrength, blinkRate) { var animationSettings = JSON.stringify({ fps: 30, frameIndex: 0, running: true, firstFrame: 0, lastFrame: 30, - loop: false }); + loop: true }); this.entity = Entities.addEntity({ type: "ParticleEffect", animationSettings: animationSettings, position: spawnPoint, textures: "http://www.hyperlogic.org/images/particle.png", - emitRate: 30, - emitStrength: 3, - color: { red: 128, green: 255, blue: 255 }, + emitRate: emitRate, + emitStrength: emitStrength, + emitDirection: emitDirection, + color: color, visible: true, locked: false }); + this.isPlaying = true; + var self = this; this.timer = Script.setInterval(function () { - print("AJT: setting animation props"); - var animProp = { animationFrameIndex: 0, - animationIsPlaying: true }; + // flip is playing state + self.isPlaying = !self.isPlaying; + var animProp = { animationIsPlaying: self.isPlaying }; Entities.editEntity(self.entity, animProp); - }, 2000); - + }, (1 / blinkRate) * 1000); } TestFx.prototype.Destroy = function () { @@ -65,11 +67,19 @@ var objs = []; function Init() { objs.push(new TestBox()); - objs.push(new TestFx()); + objs.push(new TestFx({ red: 255, blue: 0, green: 0 }, + { x: 0.5, y: 1.0, z: 0.0 }, + 100, 3, 1)); + objs.push(new TestFx({ red: 0, blue: 255, green: 0 }, + { x: 0, y: 1, z: 0 }, + 1000, 5, 0.5)); + objs.push(new TestFx({ red: 0, blue: 0, green: 255 }, + { x: -0.5, y: 1, z: 0 }, + 100, 3, 1)); } function ShutDown() { - var i, len = entities.length; + var i, len = objs.length; for (i = 0; i < len; i++) { objs[i].Destroy(); } diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 32fd06c837..009d26481a 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -34,8 +34,7 @@ void RenderableParticleEffectEntityItem::render(RenderArgs* args) { assert(getType() == EntityTypes::ParticleEffect); PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render"); - if (_texturesChangedFlag) - { + if (_texturesChangedFlag) { if (_textures.isEmpty()) { _texture.clear(); } else { @@ -57,35 +56,38 @@ void RenderableParticleEffectEntityItem::render(RenderArgs* args) { void RenderableParticleEffectEntityItem::renderUntexturedQuads(RenderArgs* args) { - float pa_rad = getParticleRadius(); + float particleRadius = getParticleRadius(); const float MAX_COLOR = 255.0f; - glm::vec4 paColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, - getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); + glm::vec4 particleColor(getColor()[RED_INDEX] / MAX_COLOR, + getColor()[GREEN_INDEX] / MAX_COLOR, + getColor()[BLUE_INDEX] / MAX_COLOR, + getLocalRenderAlpha()); - glm::vec3 up_offset = args->_viewFrustum->getUp() * pa_rad; - glm::vec3 right_offset = args->_viewFrustum->getRight() * pa_rad; + glm::vec3 upOffset = args->_viewFrustum->getUp() * particleRadius; + glm::vec3 rightOffset = args->_viewFrustum->getRight() * particleRadius; - QVector* pointVec = new QVector(_paCount * VERTS_PER_PARTICLE); - quint32 paCount = 0; - quint32 paIter = _paHead; - while (_paLife[paIter] > 0.0f && paCount < _maxParticles) { - int j = paIter * XYZ_STRIDE; + QVector vertices(getLivingParticleCount() * VERTS_PER_PARTICLE); + quint32 count = 0; + for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { + glm::vec3 pos = _particlePositions[i]; - glm::vec3 pos(_paPosition[j], _paPosition[j + 1], _paPosition[j + 2]); - - pointVec->append(pos - right_offset + up_offset); - pointVec->append(pos + right_offset + up_offset); - pointVec->append(pos + right_offset - up_offset); - pointVec->append(pos - right_offset - up_offset); - - paIter = (paIter + 1) % _maxParticles; - paCount++; + // generate corners of quad, aligned to face the camera + vertices.append(pos - rightOffset + upOffset); + vertices.append(pos + rightOffset + upOffset); + vertices.append(pos + rightOffset - upOffset); + vertices.append(pos - rightOffset - upOffset); + count++; } - DependencyManager::get()->updateVertices(_cacheID, *pointVec, paColor); + // just double checking, cause if this invarient is false, we might have memory corruption bugs. + assert(count == getLivingParticleCount()); + + // update geometry cache with all the verts in model coordinates. + DependencyManager::get()->updateVertices(_cacheID, vertices, particleColor); glPushMatrix(); + glm::vec3 position = getPosition(); glTranslatef(position.x, position.y, position.z); glm::quat rotation = getRotation(); @@ -93,92 +95,93 @@ void RenderableParticleEffectEntityItem::renderUntexturedQuads(RenderArgs* args) glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); glPushMatrix(); + glm::vec3 positionToCenter = getCenter() - position; glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); DependencyManager::get()->renderVertices(gpu::QUADS, _cacheID); - glPopMatrix(); - glPopMatrix(); - delete pointVec; + glPopMatrix(); + + glPopMatrix(); } -static glm::vec3 s_dir; +static glm::vec3 zSortAxis; static bool zSort(const glm::vec3& rhs, const glm::vec3& lhs) { - return glm::dot(rhs, s_dir) > glm::dot(lhs, s_dir); + return glm::dot(rhs, ::zSortAxis) > glm::dot(lhs, ::zSortAxis); } void RenderableParticleEffectEntityItem::renderTexturedQuads(RenderArgs* args) { - float pa_rad = getParticleRadius(); + float particleRadius = getParticleRadius(); const float MAX_COLOR = 255.0f; - glm::vec4 paColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, - getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); + glm::vec4 particleColor(getColor()[RED_INDEX] / MAX_COLOR, + getColor()[GREEN_INDEX] / MAX_COLOR, + getColor()[BLUE_INDEX] / MAX_COLOR, + getLocalRenderAlpha()); - QVector* posVec = new QVector(_paCount); - quint32 paCount = 0; - quint32 paIter = _paHead; - while (_paLife[paIter] > 0.0f && paCount < _maxParticles) { - int j = paIter * XYZ_STRIDE; - posVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1], _paPosition[j + 2])); - - paIter = (paIter + 1) % _maxParticles; - paCount++; + QVector positions(getLivingParticleCount()); + quint32 count = 0; + for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { + positions.append(_particlePositions[i]); + count++; } + // just double checking, cause if this invarient is false, we might have memory corruption bugs. + assert(count == getLivingParticleCount()); + // sort particles back to front - qSort(posVec->begin(), posVec->end(), zSort); + ::zSortAxis = args->_viewFrustum->getDirection(); + qSort(positions.begin(), positions.end(), zSort); - QVector* vertVec = new QVector(_paCount * VERTS_PER_PARTICLE); - QVector* texCoordVec = new QVector(_paCount * VERTS_PER_PARTICLE); + QVector vertices(getLivingParticleCount() * VERTS_PER_PARTICLE); + QVector textureCoords(getLivingParticleCount() * VERTS_PER_PARTICLE); - s_dir = args->_viewFrustum->getDirection(); - glm::vec3 up_offset = args->_viewFrustum->getUp() * pa_rad; - glm::vec3 right_offset = args->_viewFrustum->getRight() * pa_rad; + glm::vec3 upOffset = args->_viewFrustum->getUp() * particleRadius; + glm::vec3 rightOffset = args->_viewFrustum->getRight() * particleRadius; - for (int i = 0; i < posVec->size(); i++) { - glm::vec3 pos = posVec->at(i); + for (int i = 0; i < positions.size(); i++) { + glm::vec3 pos = positions[i]; - vertVec->append(pos - right_offset + up_offset); - vertVec->append(pos + right_offset + up_offset); - vertVec->append(pos + right_offset - up_offset); - vertVec->append(pos - right_offset - up_offset); + // generate corners of quad aligned to face the camera. + vertices.append(pos - rightOffset + upOffset); + vertices.append(pos + rightOffset + upOffset); + vertices.append(pos + rightOffset - upOffset); + vertices.append(pos - rightOffset - upOffset); - texCoordVec->append(glm::vec2(0, 1)); - texCoordVec->append(glm::vec2(1, 1)); - texCoordVec->append(glm::vec2(1, 0)); - texCoordVec->append(glm::vec2(0, 0)); + textureCoords.append(glm::vec2(0, 1)); + textureCoords.append(glm::vec2(1, 1)); + textureCoords.append(glm::vec2(1, 0)); + textureCoords.append(glm::vec2(0, 0)); } - DependencyManager::get()->updateVertices(_cacheID, *vertVec, *texCoordVec, paColor); + // update geometry cache with all the verts in model coordinates. + DependencyManager::get()->updateVertices(_cacheID, vertices, textureCoords, particleColor); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, _texture->getID()); glPushMatrix(); - glm::vec3 position = getPosition(); - glTranslatef(position.x, position.y, position.z); - glm::quat rotation = getRotation(); - glm::vec3 axis = glm::axis(rotation); - glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glm::vec3 position = getPosition(); + glTranslatef(position.x, position.y, position.z); + glm::quat rotation = getRotation(); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); - glPushMatrix(); + glPushMatrix(); - glm::vec3 positionToCenter = getCenter() - position; - glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + glm::vec3 positionToCenter = getCenter() - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); - DependencyManager::get()->renderVertices(gpu::QUADS, _cacheID); + DependencyManager::get()->renderVertices(gpu::QUADS, _cacheID); + + glPopMatrix(); - glPopMatrix(); glPopMatrix(); glDisable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); - - delete posVec; - delete vertVec; - delete texCoordVec; } diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index fc4927fdcd..d5492cb485 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -41,6 +41,7 @@ #include "EntitiesLogging.h" #include "ParticleEffectEntityItem.h" +const xColor ParticleEffectEntityItem::DEFAULT_COLOR = { 255, 255, 255 }; const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FRAME_INDEX = 0.0f; const bool ParticleEffectEntityItem::DEFAULT_ANIMATION_IS_PLAYING = false; const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FPS = 30.0f; @@ -53,42 +54,42 @@ const float ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY = -9.8f; const float ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS = 0.025f; const QString ParticleEffectEntityItem::DEFAULT_TEXTURES = ""; + EntityItem* ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return new ParticleEffectEntityItem(entityID, properties); } // our non-pure virtual subclass for now... ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID, properties) { + EntityItem(entityItemID, properties), + _maxParticles(DEFAULT_MAX_PARTICLES), + _lifespan(DEFAULT_LIFESPAN), + _emitRate(DEFAULT_EMIT_RATE), + _emitDirection(DEFAULT_EMIT_DIRECTION), + _emitStrength(DEFAULT_EMIT_STRENGTH), + _localGravity(DEFAULT_LOCAL_GRAVITY), + _particleRadius(DEFAULT_PARTICLE_RADIUS), + _lastAnimated(usecTimestampNow()), + _animationLoop(), + _animationSettings(), + _textures(DEFAULT_TEXTURES), + _texturesChangedFlag(false), + _shapeType(SHAPE_TYPE_NONE), + _particleLifetimes(DEFAULT_MAX_PARTICLES, 0.0f), + _particlePositions(DEFAULT_MAX_PARTICLES, glm::vec3(0.0f, 0.0f, 0.0f)), + _particleVelocities(DEFAULT_MAX_PARTICLES, glm::vec3(0.0f, 0.0f, 0.0f)), + _timeUntilNextEmit(0.0f), + _particleHeadIndex(0), + _particleTailIndex(0), + _particleMaxBound(glm::vec3(1.0f, 1.0f, 1.0f)), + _particleMinBound(glm::vec3(-1.0f, -1.0f, -1.0f)) { + _type = EntityTypes::ParticleEffect; - _maxParticles = DEFAULT_MAX_PARTICLES; - _lifespan = DEFAULT_LIFESPAN; - _emitRate = DEFAULT_EMIT_RATE; - _emitDirection = DEFAULT_EMIT_DIRECTION; - _emitStrength = DEFAULT_EMIT_STRENGTH; - _localGravity = DEFAULT_LOCAL_GRAVITY; - _particleRadius = DEFAULT_PARTICLE_RADIUS; - _textures = DEFAULT_TEXTURES; + setColor(DEFAULT_COLOR); setProperties(properties); - // this is a pretty dumb thing to do, and it should probably be changed to use a more dynamic - // data structure in the future. I'm just trying to get some code out the door for now (and it's - // at least time efficient (though not space efficient). - // Also, this being a real-time application, it's doubtful we'll ever have millions of particles - // to keep track of, so this really isn't all that bad. - _paLife = new float[_maxParticles]; - _paPosition = new float[_maxParticles * XYZ_STRIDE]; // x,y,z - _paVelocity = new float[_maxParticles * XYZ_STRIDE]; // x,y,z - _paXmax = _paYmax = _paZmax = 1.0f; - _paXmin = _paYmin = _paZmin = -1.0f; - _randSeed = (unsigned int) glm::abs(_lifespan + _emitRate + _localGravity + getPosition().x + getPosition().y + getPosition().z); - resetSimulation(); - _lastAnimated = usecTimestampNow(); } ParticleEffectEntityItem::~ParticleEffectEntityItem() { - delete [] _paLife; - delete [] _paPosition; - delete [] _paVelocity; } EntityItemProperties ParticleEffectEntityItem::getProperties() const { @@ -146,8 +147,8 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert } int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { int bytesRead = 0; const unsigned char* dataAt = data; @@ -214,12 +215,12 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea } void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor()); @@ -240,7 +241,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, bool ParticleEffectEntityItem::isAnimatingSomething() const { // keep animating if there are particles still alive. - return (getAnimationIsPlaying() || _paCount > 0) && getAnimationFPS() != 0.0f; + return (getAnimationIsPlaying() || getLivingParticleCount() > 0) && getAnimationFPS() != 0.0f; } bool ParticleEffectEntityItem::needsToCallUpdate() const { @@ -262,9 +263,9 @@ void ParticleEffectEntityItem::update(const quint64& now) { // update the dimensions glm::vec3 dims; - dims.x = glm::max(glm::abs(_paXmin), glm::abs(_paXmax)) * 2.0f; - dims.y = glm::max(glm::abs(_paYmin), glm::abs(_paYmax)) * 2.0f; - dims.z = glm::max(glm::abs(_paZmin), glm::abs(_paZmax)) * 2.0f; + dims.x = glm::max(glm::abs(_particleMinBound.x), glm::abs(_particleMaxBound.x)) * 2.0f; + dims.y = glm::max(glm::abs(_particleMinBound.y), glm::abs(_particleMaxBound.y)) * 2.0f; + dims.z = glm::max(glm::abs(_particleMinBound.z), glm::abs(_particleMaxBound.z)) * 2.0f; setDimensions(dims); } @@ -412,106 +413,103 @@ QString ParticleEffectEntityItem::getAnimationSettings() const { return jsonByteString; } +void ParticleEffectEntityItem::extendBounds(const glm::vec3& point) { + _particleMinBound.x = glm::min(_particleMinBound.x, point.x); + _particleMinBound.y = glm::min(_particleMinBound.y, point.y); + _particleMinBound.z = glm::min(_particleMinBound.z, point.z); + _particleMaxBound.x = glm::max(_particleMaxBound.x, point.x); + _particleMaxBound.y = glm::max(_particleMaxBound.y, point.y); + _particleMaxBound.z = glm::max(_particleMaxBound.z, point.z); +} + +void ParticleEffectEntityItem::integrateParticle(quint32 index, float deltaTime) { + glm::vec3 atSquared(0.0f, 0.5f * _localGravity * deltaTime * deltaTime, 0.0f); + glm::vec3 at(0.0f, _localGravity * deltaTime, 0.0f); + _particlePositions[index] += _particleVelocities[index] * deltaTime + atSquared; + _particleVelocities[index] += at; +} + void ParticleEffectEntityItem::stepSimulation(float deltaTime) { - _paXmin = _paYmin = _paZmin = -1.0f; - _paXmax = _paYmax = _paZmax = 1.0f; - float oneHalfATSquared = 0.5f * _localGravity * deltaTime * deltaTime; + _particleMinBound = glm::vec3(-1.0f, -1.0f, -1.0f); + _particleMaxBound = glm::vec3(1.0f, 1.0f, 1.0f); - // update particles - quint32 updateCount = 0; - quint32 updateIter = _paHead; - while (_paLife[updateIter] > 0.0f && updateCount < _maxParticles) { - _paLife[updateIter] -= deltaTime; - if (_paLife[updateIter] <= 0.0f) { - _paLife[updateIter] = -1.0f; - _paHead = (_paHead + 1) % _maxParticles; - _paCount--; + // update particles between head and tail + for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { + _particleLifetimes[i] -= deltaTime; + + // if particle has died. + if (_particleLifetimes[i] <= 0.0f) { + // move head forward + _particleHeadIndex = (_particleHeadIndex + 1) % _maxParticles; } else { - int j = updateIter * XYZ_STRIDE; - - _paPosition[j] += _paVelocity[j] * deltaTime; - _paPosition[j+1] += _paVelocity[j+1] * deltaTime + oneHalfATSquared; - _paPosition[j+2] += _paVelocity[j+2] * deltaTime; - - _paXmin = glm::min(_paXmin, _paPosition[j]); - _paYmin = glm::min(_paYmin, _paPosition[j+1]); - _paZmin = glm::min(_paZmin, _paPosition[j+2]); - _paXmax = glm::max(_paXmax, _paPosition[j]); - _paYmax = glm::max(_paYmax, _paPosition[j + 1]); - _paZmax = glm::max(_paZmax, _paPosition[j + 2]); - - // massless particles - _paVelocity[j + 1] += deltaTime * _localGravity; + integrateParticle(i, deltaTime); + extendBounds(_particlePositions[i]); } - updateIter = (updateIter + 1) % _maxParticles; - updateCount++; } - quint32 emitIdx = updateIter; - quint32 birthed = 0; - glm::vec3 randOffset; - // emit new particles, but only if animaiton is playing if (getAnimationIsPlaying()) { - _partialEmit += ((float)_emitRate) * deltaTime; - birthed = (quint32)_partialEmit; - _partialEmit -= (float)birthed; - } else { - _partialEmit = 0; - } - for (quint32 i = 0; i < birthed; i++) { - if (_paLife[emitIdx] < 0.0f) { - int j = emitIdx * XYZ_STRIDE; - _paLife[emitIdx] = _lifespan; + float timeLeftInFrame = deltaTime; + while (_timeUntilNextEmit < timeLeftInFrame) { - randOffset.x = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; - randOffset.y = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; - randOffset.z = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; + timeLeftInFrame -= _timeUntilNextEmit; + _timeUntilNextEmit = 1.0f / _emitRate; - _paVelocity[j] = (_emitDirection.x * _emitStrength) + randOffset.x; - _paVelocity[j + 1] = (_emitDirection.y * _emitStrength) + randOffset.y; - _paVelocity[j + 2] = (_emitDirection.z * _emitStrength) + randOffset.z; + // emit a new particle at tail index. + quint32 i = _particleTailIndex; + _particleLifetimes[i] = _lifespan; - _paPosition[j] += _paVelocity[j] * deltaTime; - _paPosition[j + 1] += _paVelocity[j + 1] * deltaTime + oneHalfATSquared; - _paPosition[j + 2] += _paVelocity[j + 2] * deltaTime; + // jitter the _emitDirection by a random offset + glm::vec3 randOffset; + randOffset.x = (randFloat() - 0.5f) * 0.25f * _emitStrength; + randOffset.y = (randFloat() - 0.5f) * 0.25f * _emitStrength; + randOffset.z = (randFloat() - 0.5f) * 0.25f * _emitStrength; - _paXmin = glm::min(_paXmin, _paPosition[j]); - _paYmin = glm::min(_paYmin, _paPosition[j + 1]); - _paZmin = glm::min(_paZmin, _paPosition[j + 2]); - _paXmax = glm::max(_paXmax, _paPosition[j]); - _paYmax = glm::max(_paYmax, _paPosition[j + 1]); - _paZmax = glm::max(_paZmax, _paPosition[j + 2]); + // set initial conditions + _particlePositions[i] = glm::vec3(0.0f, 0.0f, 0.0f); + _particleVelocities[i] = _emitDirection * _emitStrength + randOffset; - // massless particles - // and simple gravity down - _paVelocity[j + 1] += deltaTime * _localGravity; + integrateParticle(i, timeLeftInFrame); + extendBounds(_particlePositions[i]); - emitIdx = (emitIdx + 1) % _maxParticles; - _paCount++; + _particleTailIndex = (_particleTailIndex + 1) % _maxParticles; + + // overflow! move head forward by one. + // because the case of head == tail indicates an empty array, not a full one. + // This can drop an existing older particle, but this is by design, newer particles are a higher priority. + if (_particleTailIndex == _particleHeadIndex) { + _particleHeadIndex = (_particleHeadIndex + 1) % _maxParticles; + } } - else - break; + + _timeUntilNextEmit -= timeLeftInFrame; } } -void ParticleEffectEntityItem::resetSimulation() { - for (quint32 i = 0; i < _maxParticles; i++) { - quint32 j = i * XYZ_STRIDE; - _paLife[i] = -1.0f; - _paPosition[j] = 0.0f; - _paPosition[j+1] = 0.0f; - _paPosition[j+2] = 0.0f; - _paVelocity[j] = 0.0f; - _paVelocity[j+1] = 0.0f; - _paVelocity[j+2] = 0.0f; - } - _paCount = 0; - _paHead = 0; - _partialEmit = 0.0f; +void ParticleEffectEntityItem::setMaxParticles(quint32 maxParticles) { + _maxParticles = maxParticles; - srand(_randSeed); + // TODO: try to do something smart here and preserve the state of existing particles. + + // resize vectors + _particleLifetimes.resize(_maxParticles); + _particlePositions.resize(_maxParticles); + _particleVelocities.resize(_maxParticles); + + // effectivly clear all particles and start emitting new ones from scratch. + _particleHeadIndex = 0; + _particleTailIndex = 0; + _timeUntilNextEmit = 0.0f; +} + +// because particles are in a ring buffer, this isn't trivial +quint32 ParticleEffectEntityItem::getLivingParticleCount() const { + if (_particleTailIndex >= _particleHeadIndex) { + return _particleTailIndex - _particleHeadIndex; + } else { + return (_maxParticles - _particleHeadIndex) + _particleTailIndex; + } } diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 3533ce84e7..6d1ef601f6 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -31,16 +31,16 @@ public: virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const; + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const; virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData); + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData); virtual void update(const quint64& now); virtual bool needsToCallUpdate() const; @@ -48,6 +48,7 @@ public: const rgbColor& getColor() const { return _color; } xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } + static const xColor DEFAULT_COLOR; void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } void setColor(const xColor& value) { _color[RED_INDEX] = value.red; @@ -86,7 +87,7 @@ public: float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } static const quint32 DEFAULT_MAX_PARTICLES; - void setMaxParticles(quint32 maxParticles) { _maxParticles = maxParticles; } + void setMaxParticles(quint32 maxParticles); quint32 getMaxParticles() const { return _maxParticles; } static const float DEFAULT_LIFESPAN; @@ -98,7 +99,7 @@ public: float getEmitRate() const { return _emitRate; } static const glm::vec3 DEFAULT_EMIT_DIRECTION; - void setEmitDirection(glm::vec3 emitDirection) { _emitDirection = emitDirection; } + void setEmitDirection(glm::vec3 emitDirection) { _emitDirection = glm::normalize(emitDirection); } const glm::vec3& getEmitDirection() const { return _emitDirection; } static const float DEFAULT_EMIT_STRENGTH; @@ -131,7 +132,9 @@ protected: bool isAnimatingSomething() const; void stepSimulation(float deltaTime); - void resetSimulation(); + void extendBounds(const glm::vec3& point); + void integrateParticle(quint32 index, float deltaTime); + quint32 getLivingParticleCount() const; // the properties of this entity rgbColor _color; @@ -150,21 +153,19 @@ protected: ShapeType _shapeType = SHAPE_TYPE_NONE; // all the internals of running the particle sim - const quint32 XYZ_STRIDE = 3; - float* _paLife; - float* _paPosition; - float* _paVelocity; - float _partialEmit; - quint32 _paCount; - quint32 _paHead; - float _paXmin; - float _paXmax; - float _paYmin; - float _paYmax; - float _paZmin; - float _paZmax; - unsigned int _randSeed; + QVector _particleLifetimes; + QVector _particlePositions; + QVector _particleVelocities; + float _timeUntilNextEmit; + // particle arrays are a ring buffer, use these indicies + // to keep track of the living particles. + quint32 _particleHeadIndex; + quint32 _particleTailIndex; + + // bounding volume + glm::vec3 _particleMaxBound; + glm::vec3 _particleMinBound; }; #endif // hifi_ParticleEffectEntityItem_h diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index ec83e18c3d..87851ed80e 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -74,7 +74,7 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeEntityAddOrEdit: case PacketTypeEntityData: - return VERSION_ENTITIES_ZONE_ENTITIES_HAVE_SKYBOX; + return VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES; case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 38aeed4993..82fc927166 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -142,5 +142,6 @@ const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_DYNAMIC_SHAPE = 18; const PacketVersion VERSION_ENTITIES_HAVE_NAMES = 19; const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_ATMOSPHERE = 20; const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_SKYBOX = 21; +const PacketVersion VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES = 22; #endif // hifi_PacketHeaders_h