mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:44:11 +02:00
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:
parent
ede42285b1
commit
b3af515224
6 changed files with 238 additions and 225 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue