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.
This commit is contained in:
Anthony Thibault 2015-05-11 19:21:33 -07:00
parent ede42285b1
commit b3af515224
6 changed files with 238 additions and 225 deletions

View file

@ -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();
}

View file

@ -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<glm::vec3>* pointVec = new QVector<glm::vec3>(_paCount * VERTS_PER_PARTICLE);
quint32 paCount = 0;
quint32 paIter = _paHead;
while (_paLife[paIter] > 0.0f && paCount < _maxParticles) {
int j = paIter * XYZ_STRIDE;
QVector<glm::vec3> 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<GeometryCache>()->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<GeometryCache>()->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<GeometryCache>()->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<glm::vec3>* posVec = new QVector<glm::vec3>(_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<glm::vec3> 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<glm::vec3>* vertVec = new QVector<glm::vec3>(_paCount * VERTS_PER_PARTICLE);
QVector<glm::vec2>* texCoordVec = new QVector<glm::vec2>(_paCount * VERTS_PER_PARTICLE);
QVector<glm::vec3> vertices(getLivingParticleCount() * VERTS_PER_PARTICLE);
QVector<glm::vec2> 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<GeometryCache>()->updateVertices(_cacheID, *vertVec, *texCoordVec, paColor);
// update geometry cache with all the verts in model coordinates.
DependencyManager::get<GeometryCache>()->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<GeometryCache>()->renderVertices(gpu::QUADS, _cacheID);
DependencyManager::get<GeometryCache>()->renderVertices(gpu::QUADS, _cacheID);
glPopMatrix();
glPopMatrix();
glPopMatrix();
glDisable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
delete posVec;
delete vertVec;
delete texCoordVec;
}

View file

@ -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;
}
}

View file

@ -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<float> _particleLifetimes;
QVector<glm::vec3> _particlePositions;
QVector<glm::vec3> _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

View file

@ -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:

View file

@ -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