mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 05:17:02 +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
|
// constructor
|
||||||
function TestFx() {
|
function TestFx(color, emitDirection, emitRate, emitStrength, blinkRate) {
|
||||||
var animationSettings = JSON.stringify({ fps: 30,
|
var animationSettings = JSON.stringify({ fps: 30,
|
||||||
frameIndex: 0,
|
frameIndex: 0,
|
||||||
running: true,
|
running: true,
|
||||||
firstFrame: 0,
|
firstFrame: 0,
|
||||||
lastFrame: 30,
|
lastFrame: 30,
|
||||||
loop: false });
|
loop: true });
|
||||||
|
|
||||||
this.entity = Entities.addEntity({ type: "ParticleEffect",
|
this.entity = Entities.addEntity({ type: "ParticleEffect",
|
||||||
animationSettings: animationSettings,
|
animationSettings: animationSettings,
|
||||||
position: spawnPoint,
|
position: spawnPoint,
|
||||||
textures: "http://www.hyperlogic.org/images/particle.png",
|
textures: "http://www.hyperlogic.org/images/particle.png",
|
||||||
emitRate: 30,
|
emitRate: emitRate,
|
||||||
emitStrength: 3,
|
emitStrength: emitStrength,
|
||||||
color: { red: 128, green: 255, blue: 255 },
|
emitDirection: emitDirection,
|
||||||
|
color: color,
|
||||||
visible: true,
|
visible: true,
|
||||||
locked: false });
|
locked: false });
|
||||||
|
|
||||||
|
this.isPlaying = true;
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this.timer = Script.setInterval(function () {
|
this.timer = Script.setInterval(function () {
|
||||||
print("AJT: setting animation props");
|
// flip is playing state
|
||||||
var animProp = { animationFrameIndex: 0,
|
self.isPlaying = !self.isPlaying;
|
||||||
animationIsPlaying: true };
|
var animProp = { animationIsPlaying: self.isPlaying };
|
||||||
Entities.editEntity(self.entity, animProp);
|
Entities.editEntity(self.entity, animProp);
|
||||||
}, 2000);
|
}, (1 / blinkRate) * 1000);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TestFx.prototype.Destroy = function () {
|
TestFx.prototype.Destroy = function () {
|
||||||
|
@ -65,11 +67,19 @@
|
||||||
var objs = [];
|
var objs = [];
|
||||||
function Init() {
|
function Init() {
|
||||||
objs.push(new TestBox());
|
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() {
|
function ShutDown() {
|
||||||
var i, len = entities.length;
|
var i, len = objs.length;
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
objs[i].Destroy();
|
objs[i].Destroy();
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,7 @@ void RenderableParticleEffectEntityItem::render(RenderArgs* args) {
|
||||||
assert(getType() == EntityTypes::ParticleEffect);
|
assert(getType() == EntityTypes::ParticleEffect);
|
||||||
PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render");
|
PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render");
|
||||||
|
|
||||||
if (_texturesChangedFlag)
|
if (_texturesChangedFlag) {
|
||||||
{
|
|
||||||
if (_textures.isEmpty()) {
|
if (_textures.isEmpty()) {
|
||||||
_texture.clear();
|
_texture.clear();
|
||||||
} else {
|
} else {
|
||||||
|
@ -57,35 +56,38 @@ void RenderableParticleEffectEntityItem::render(RenderArgs* args) {
|
||||||
|
|
||||||
void RenderableParticleEffectEntityItem::renderUntexturedQuads(RenderArgs* args) {
|
void RenderableParticleEffectEntityItem::renderUntexturedQuads(RenderArgs* args) {
|
||||||
|
|
||||||
float pa_rad = getParticleRadius();
|
float particleRadius = getParticleRadius();
|
||||||
|
|
||||||
const float MAX_COLOR = 255.0f;
|
const float MAX_COLOR = 255.0f;
|
||||||
glm::vec4 paColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR,
|
glm::vec4 particleColor(getColor()[RED_INDEX] / MAX_COLOR,
|
||||||
getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha());
|
getColor()[GREEN_INDEX] / MAX_COLOR,
|
||||||
|
getColor()[BLUE_INDEX] / MAX_COLOR,
|
||||||
|
getLocalRenderAlpha());
|
||||||
|
|
||||||
glm::vec3 up_offset = args->_viewFrustum->getUp() * pa_rad;
|
glm::vec3 upOffset = args->_viewFrustum->getUp() * particleRadius;
|
||||||
glm::vec3 right_offset = args->_viewFrustum->getRight() * pa_rad;
|
glm::vec3 rightOffset = args->_viewFrustum->getRight() * particleRadius;
|
||||||
|
|
||||||
QVector<glm::vec3>* pointVec = new QVector<glm::vec3>(_paCount * VERTS_PER_PARTICLE);
|
QVector<glm::vec3> vertices(getLivingParticleCount() * VERTS_PER_PARTICLE);
|
||||||
quint32 paCount = 0;
|
quint32 count = 0;
|
||||||
quint32 paIter = _paHead;
|
for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) {
|
||||||
while (_paLife[paIter] > 0.0f && paCount < _maxParticles) {
|
glm::vec3 pos = _particlePositions[i];
|
||||||
int j = paIter * XYZ_STRIDE;
|
|
||||||
|
|
||||||
glm::vec3 pos(_paPosition[j], _paPosition[j + 1], _paPosition[j + 2]);
|
// generate corners of quad, aligned to face the camera
|
||||||
|
vertices.append(pos - rightOffset + upOffset);
|
||||||
pointVec->append(pos - right_offset + up_offset);
|
vertices.append(pos + rightOffset + upOffset);
|
||||||
pointVec->append(pos + right_offset + up_offset);
|
vertices.append(pos + rightOffset - upOffset);
|
||||||
pointVec->append(pos + right_offset - up_offset);
|
vertices.append(pos - rightOffset - upOffset);
|
||||||
pointVec->append(pos - right_offset - up_offset);
|
count++;
|
||||||
|
|
||||||
paIter = (paIter + 1) % _maxParticles;
|
|
||||||
paCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
glPushMatrix();
|
||||||
|
|
||||||
glm::vec3 position = getPosition();
|
glm::vec3 position = getPosition();
|
||||||
glTranslatef(position.x, position.y, position.z);
|
glTranslatef(position.x, position.y, position.z);
|
||||||
glm::quat rotation = getRotation();
|
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);
|
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||||
|
|
||||||
glPushMatrix();
|
glPushMatrix();
|
||||||
|
|
||||||
glm::vec3 positionToCenter = getCenter() - position;
|
glm::vec3 positionToCenter = getCenter() - position;
|
||||||
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
|
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
|
||||||
|
|
||||||
DependencyManager::get<GeometryCache>()->renderVertices(gpu::QUADS, _cacheID);
|
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) {
|
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) {
|
void RenderableParticleEffectEntityItem::renderTexturedQuads(RenderArgs* args) {
|
||||||
|
|
||||||
float pa_rad = getParticleRadius();
|
float particleRadius = getParticleRadius();
|
||||||
|
|
||||||
const float MAX_COLOR = 255.0f;
|
const float MAX_COLOR = 255.0f;
|
||||||
glm::vec4 paColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR,
|
glm::vec4 particleColor(getColor()[RED_INDEX] / MAX_COLOR,
|
||||||
getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha());
|
getColor()[GREEN_INDEX] / MAX_COLOR,
|
||||||
|
getColor()[BLUE_INDEX] / MAX_COLOR,
|
||||||
|
getLocalRenderAlpha());
|
||||||
|
|
||||||
QVector<glm::vec3>* posVec = new QVector<glm::vec3>(_paCount);
|
QVector<glm::vec3> positions(getLivingParticleCount());
|
||||||
quint32 paCount = 0;
|
quint32 count = 0;
|
||||||
quint32 paIter = _paHead;
|
for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) {
|
||||||
while (_paLife[paIter] > 0.0f && paCount < _maxParticles) {
|
positions.append(_particlePositions[i]);
|
||||||
int j = paIter * XYZ_STRIDE;
|
count++;
|
||||||
posVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1], _paPosition[j + 2]));
|
|
||||||
|
|
||||||
paIter = (paIter + 1) % _maxParticles;
|
|
||||||
paCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// just double checking, cause if this invarient is false, we might have memory corruption bugs.
|
||||||
|
assert(count == getLivingParticleCount());
|
||||||
|
|
||||||
// sort particles back to front
|
// 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::vec3> vertices(getLivingParticleCount() * VERTS_PER_PARTICLE);
|
||||||
QVector<glm::vec2>* texCoordVec = new QVector<glm::vec2>(_paCount * VERTS_PER_PARTICLE);
|
QVector<glm::vec2> textureCoords(getLivingParticleCount() * VERTS_PER_PARTICLE);
|
||||||
|
|
||||||
s_dir = args->_viewFrustum->getDirection();
|
glm::vec3 upOffset = args->_viewFrustum->getUp() * particleRadius;
|
||||||
glm::vec3 up_offset = args->_viewFrustum->getUp() * pa_rad;
|
glm::vec3 rightOffset = args->_viewFrustum->getRight() * particleRadius;
|
||||||
glm::vec3 right_offset = args->_viewFrustum->getRight() * pa_rad;
|
|
||||||
|
|
||||||
for (int i = 0; i < posVec->size(); i++) {
|
for (int i = 0; i < positions.size(); i++) {
|
||||||
glm::vec3 pos = posVec->at(i);
|
glm::vec3 pos = positions[i];
|
||||||
|
|
||||||
vertVec->append(pos - right_offset + up_offset);
|
// generate corners of quad aligned to face the camera.
|
||||||
vertVec->append(pos + right_offset + up_offset);
|
vertices.append(pos - rightOffset + upOffset);
|
||||||
vertVec->append(pos + right_offset - up_offset);
|
vertices.append(pos + rightOffset + upOffset);
|
||||||
vertVec->append(pos - right_offset - up_offset);
|
vertices.append(pos + rightOffset - upOffset);
|
||||||
|
vertices.append(pos - rightOffset - upOffset);
|
||||||
|
|
||||||
texCoordVec->append(glm::vec2(0, 1));
|
textureCoords.append(glm::vec2(0, 1));
|
||||||
texCoordVec->append(glm::vec2(1, 1));
|
textureCoords.append(glm::vec2(1, 1));
|
||||||
texCoordVec->append(glm::vec2(1, 0));
|
textureCoords.append(glm::vec2(1, 0));
|
||||||
texCoordVec->append(glm::vec2(0, 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);
|
glEnable(GL_TEXTURE_2D);
|
||||||
glBindTexture(GL_TEXTURE_2D, _texture->getID());
|
glBindTexture(GL_TEXTURE_2D, _texture->getID());
|
||||||
|
|
||||||
glPushMatrix();
|
glPushMatrix();
|
||||||
|
|
||||||
glm::vec3 position = getPosition();
|
glm::vec3 position = getPosition();
|
||||||
glTranslatef(position.x, position.y, position.z);
|
glTranslatef(position.x, position.y, position.z);
|
||||||
glm::quat rotation = getRotation();
|
glm::quat rotation = getRotation();
|
||||||
glm::vec3 axis = glm::axis(rotation);
|
glm::vec3 axis = glm::axis(rotation);
|
||||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||||
|
|
||||||
glPushMatrix();
|
glPushMatrix();
|
||||||
|
|
||||||
glm::vec3 positionToCenter = getCenter() - position;
|
glm::vec3 positionToCenter = getCenter() - position;
|
||||||
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
|
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
|
||||||
|
|
||||||
DependencyManager::get<GeometryCache>()->renderVertices(gpu::QUADS, _cacheID);
|
DependencyManager::get<GeometryCache>()->renderVertices(gpu::QUADS, _cacheID);
|
||||||
|
|
||||||
|
glPopMatrix();
|
||||||
|
|
||||||
glPopMatrix();
|
|
||||||
glPopMatrix();
|
glPopMatrix();
|
||||||
|
|
||||||
glDisable(GL_TEXTURE_2D);
|
glDisable(GL_TEXTURE_2D);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
delete posVec;
|
|
||||||
delete vertVec;
|
|
||||||
delete texCoordVec;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
#include "EntitiesLogging.h"
|
#include "EntitiesLogging.h"
|
||||||
#include "ParticleEffectEntityItem.h"
|
#include "ParticleEffectEntityItem.h"
|
||||||
|
|
||||||
|
const xColor ParticleEffectEntityItem::DEFAULT_COLOR = { 255, 255, 255 };
|
||||||
const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FRAME_INDEX = 0.0f;
|
const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FRAME_INDEX = 0.0f;
|
||||||
const bool ParticleEffectEntityItem::DEFAULT_ANIMATION_IS_PLAYING = false;
|
const bool ParticleEffectEntityItem::DEFAULT_ANIMATION_IS_PLAYING = false;
|
||||||
const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FPS = 30.0f;
|
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 float ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS = 0.025f;
|
||||||
const QString ParticleEffectEntityItem::DEFAULT_TEXTURES = "";
|
const QString ParticleEffectEntityItem::DEFAULT_TEXTURES = "";
|
||||||
|
|
||||||
|
|
||||||
EntityItem* ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
EntityItem* ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||||
return new ParticleEffectEntityItem(entityID, properties);
|
return new ParticleEffectEntityItem(entityID, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
// our non-pure virtual subclass for now...
|
// our non-pure virtual subclass for now...
|
||||||
ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
|
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;
|
_type = EntityTypes::ParticleEffect;
|
||||||
_maxParticles = DEFAULT_MAX_PARTICLES;
|
setColor(DEFAULT_COLOR);
|
||||||
_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;
|
|
||||||
setProperties(properties);
|
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() {
|
ParticleEffectEntityItem::~ParticleEffectEntityItem() {
|
||||||
delete [] _paLife;
|
|
||||||
delete [] _paPosition;
|
|
||||||
delete [] _paVelocity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityItemProperties ParticleEffectEntityItem::getProperties() const {
|
EntityItemProperties ParticleEffectEntityItem::getProperties() const {
|
||||||
|
@ -146,8 +147,8 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
|
||||||
}
|
}
|
||||||
|
|
||||||
int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||||
ReadBitstreamToTreeParams& args,
|
ReadBitstreamToTreeParams& args,
|
||||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
|
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
|
||||||
|
|
||||||
int bytesRead = 0;
|
int bytesRead = 0;
|
||||||
const unsigned char* dataAt = data;
|
const unsigned char* dataAt = data;
|
||||||
|
@ -214,12 +215,12 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||||
EntityPropertyFlags& requestedProperties,
|
EntityPropertyFlags& requestedProperties,
|
||||||
EntityPropertyFlags& propertyFlags,
|
EntityPropertyFlags& propertyFlags,
|
||||||
EntityPropertyFlags& propertiesDidntFit,
|
EntityPropertyFlags& propertiesDidntFit,
|
||||||
int& propertyCount,
|
int& propertyCount,
|
||||||
OctreeElement::AppendState& appendState) const {
|
OctreeElement::AppendState& appendState) const {
|
||||||
|
|
||||||
bool successPropertyFits = true;
|
bool successPropertyFits = true;
|
||||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
|
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor());
|
||||||
|
@ -240,7 +241,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData,
|
||||||
|
|
||||||
bool ParticleEffectEntityItem::isAnimatingSomething() const {
|
bool ParticleEffectEntityItem::isAnimatingSomething() const {
|
||||||
// keep animating if there are particles still alive.
|
// 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 {
|
bool ParticleEffectEntityItem::needsToCallUpdate() const {
|
||||||
|
@ -262,9 +263,9 @@ void ParticleEffectEntityItem::update(const quint64& now) {
|
||||||
|
|
||||||
// update the dimensions
|
// update the dimensions
|
||||||
glm::vec3 dims;
|
glm::vec3 dims;
|
||||||
dims.x = glm::max(glm::abs(_paXmin), glm::abs(_paXmax)) * 2.0f;
|
dims.x = glm::max(glm::abs(_particleMinBound.x), glm::abs(_particleMaxBound.x)) * 2.0f;
|
||||||
dims.y = glm::max(glm::abs(_paYmin), glm::abs(_paYmax)) * 2.0f;
|
dims.y = glm::max(glm::abs(_particleMinBound.y), glm::abs(_particleMaxBound.y)) * 2.0f;
|
||||||
dims.z = glm::max(glm::abs(_paZmin), glm::abs(_paZmax)) * 2.0f;
|
dims.z = glm::max(glm::abs(_particleMinBound.z), glm::abs(_particleMaxBound.z)) * 2.0f;
|
||||||
setDimensions(dims);
|
setDimensions(dims);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,106 +413,103 @@ QString ParticleEffectEntityItem::getAnimationSettings() const {
|
||||||
return jsonByteString;
|
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) {
|
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
|
// update particles between head and tail
|
||||||
quint32 updateCount = 0;
|
for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) {
|
||||||
quint32 updateIter = _paHead;
|
_particleLifetimes[i] -= deltaTime;
|
||||||
while (_paLife[updateIter] > 0.0f && updateCount < _maxParticles) {
|
|
||||||
_paLife[updateIter] -= deltaTime;
|
// if particle has died.
|
||||||
if (_paLife[updateIter] <= 0.0f) {
|
if (_particleLifetimes[i] <= 0.0f) {
|
||||||
_paLife[updateIter] = -1.0f;
|
// move head forward
|
||||||
_paHead = (_paHead + 1) % _maxParticles;
|
_particleHeadIndex = (_particleHeadIndex + 1) % _maxParticles;
|
||||||
_paCount--;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int j = updateIter * XYZ_STRIDE;
|
integrateParticle(i, deltaTime);
|
||||||
|
extendBounds(_particlePositions[i]);
|
||||||
_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;
|
|
||||||
}
|
}
|
||||||
updateIter = (updateIter + 1) % _maxParticles;
|
|
||||||
updateCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint32 emitIdx = updateIter;
|
|
||||||
quint32 birthed = 0;
|
|
||||||
glm::vec3 randOffset;
|
|
||||||
|
|
||||||
// emit new particles, but only if animaiton is playing
|
// emit new particles, but only if animaiton is playing
|
||||||
if (getAnimationIsPlaying()) {
|
if (getAnimationIsPlaying()) {
|
||||||
_partialEmit += ((float)_emitRate) * deltaTime;
|
|
||||||
birthed = (quint32)_partialEmit;
|
|
||||||
_partialEmit -= (float)birthed;
|
|
||||||
} else {
|
|
||||||
_partialEmit = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (quint32 i = 0; i < birthed; i++) {
|
float timeLeftInFrame = deltaTime;
|
||||||
if (_paLife[emitIdx] < 0.0f) {
|
while (_timeUntilNextEmit < timeLeftInFrame) {
|
||||||
int j = emitIdx * XYZ_STRIDE;
|
|
||||||
_paLife[emitIdx] = _lifespan;
|
|
||||||
|
|
||||||
randOffset.x = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength;
|
timeLeftInFrame -= _timeUntilNextEmit;
|
||||||
randOffset.y = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength;
|
_timeUntilNextEmit = 1.0f / _emitRate;
|
||||||
randOffset.z = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength;
|
|
||||||
|
|
||||||
_paVelocity[j] = (_emitDirection.x * _emitStrength) + randOffset.x;
|
// emit a new particle at tail index.
|
||||||
_paVelocity[j + 1] = (_emitDirection.y * _emitStrength) + randOffset.y;
|
quint32 i = _particleTailIndex;
|
||||||
_paVelocity[j + 2] = (_emitDirection.z * _emitStrength) + randOffset.z;
|
_particleLifetimes[i] = _lifespan;
|
||||||
|
|
||||||
_paPosition[j] += _paVelocity[j] * deltaTime;
|
// jitter the _emitDirection by a random offset
|
||||||
_paPosition[j + 1] += _paVelocity[j + 1] * deltaTime + oneHalfATSquared;
|
glm::vec3 randOffset;
|
||||||
_paPosition[j + 2] += _paVelocity[j + 2] * deltaTime;
|
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]);
|
// set initial conditions
|
||||||
_paYmin = glm::min(_paYmin, _paPosition[j + 1]);
|
_particlePositions[i] = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||||
_paZmin = glm::min(_paZmin, _paPosition[j + 2]);
|
_particleVelocities[i] = _emitDirection * _emitStrength + randOffset;
|
||||||
_paXmax = glm::max(_paXmax, _paPosition[j]);
|
|
||||||
_paYmax = glm::max(_paYmax, _paPosition[j + 1]);
|
|
||||||
_paZmax = glm::max(_paZmax, _paPosition[j + 2]);
|
|
||||||
|
|
||||||
// massless particles
|
integrateParticle(i, timeLeftInFrame);
|
||||||
// and simple gravity down
|
extendBounds(_particlePositions[i]);
|
||||||
_paVelocity[j + 1] += deltaTime * _localGravity;
|
|
||||||
|
|
||||||
emitIdx = (emitIdx + 1) % _maxParticles;
|
_particleTailIndex = (_particleTailIndex + 1) % _maxParticles;
|
||||||
_paCount++;
|
|
||||||
|
// 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() {
|
void ParticleEffectEntityItem::setMaxParticles(quint32 maxParticles) {
|
||||||
for (quint32 i = 0; i < _maxParticles; i++) {
|
_maxParticles = maxParticles;
|
||||||
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;
|
|
||||||
|
|
||||||
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 EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
|
||||||
|
|
||||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||||
EntityPropertyFlags& requestedProperties,
|
EntityPropertyFlags& requestedProperties,
|
||||||
EntityPropertyFlags& propertyFlags,
|
EntityPropertyFlags& propertyFlags,
|
||||||
EntityPropertyFlags& propertiesDidntFit,
|
EntityPropertyFlags& propertiesDidntFit,
|
||||||
int& propertyCount,
|
int& propertyCount,
|
||||||
OctreeElement::AppendState& appendState) const;
|
OctreeElement::AppendState& appendState) const;
|
||||||
|
|
||||||
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||||
ReadBitstreamToTreeParams& args,
|
ReadBitstreamToTreeParams& args,
|
||||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
|
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
|
||||||
|
|
||||||
virtual void update(const quint64& now);
|
virtual void update(const quint64& now);
|
||||||
virtual bool needsToCallUpdate() const;
|
virtual bool needsToCallUpdate() const;
|
||||||
|
@ -48,6 +48,7 @@ public:
|
||||||
const rgbColor& getColor() const { return _color; }
|
const rgbColor& getColor() const { return _color; }
|
||||||
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; 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 rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
|
||||||
void setColor(const xColor& value) {
|
void setColor(const xColor& value) {
|
||||||
_color[RED_INDEX] = value.red;
|
_color[RED_INDEX] = value.red;
|
||||||
|
@ -86,7 +87,7 @@ public:
|
||||||
float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); }
|
float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); }
|
||||||
|
|
||||||
static const quint32 DEFAULT_MAX_PARTICLES;
|
static const quint32 DEFAULT_MAX_PARTICLES;
|
||||||
void setMaxParticles(quint32 maxParticles) { _maxParticles = maxParticles; }
|
void setMaxParticles(quint32 maxParticles);
|
||||||
quint32 getMaxParticles() const { return _maxParticles; }
|
quint32 getMaxParticles() const { return _maxParticles; }
|
||||||
|
|
||||||
static const float DEFAULT_LIFESPAN;
|
static const float DEFAULT_LIFESPAN;
|
||||||
|
@ -98,7 +99,7 @@ public:
|
||||||
float getEmitRate() const { return _emitRate; }
|
float getEmitRate() const { return _emitRate; }
|
||||||
|
|
||||||
static const glm::vec3 DEFAULT_EMIT_DIRECTION;
|
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; }
|
const glm::vec3& getEmitDirection() const { return _emitDirection; }
|
||||||
|
|
||||||
static const float DEFAULT_EMIT_STRENGTH;
|
static const float DEFAULT_EMIT_STRENGTH;
|
||||||
|
@ -131,7 +132,9 @@ protected:
|
||||||
|
|
||||||
bool isAnimatingSomething() const;
|
bool isAnimatingSomething() const;
|
||||||
void stepSimulation(float deltaTime);
|
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
|
// the properties of this entity
|
||||||
rgbColor _color;
|
rgbColor _color;
|
||||||
|
@ -150,21 +153,19 @@ protected:
|
||||||
ShapeType _shapeType = SHAPE_TYPE_NONE;
|
ShapeType _shapeType = SHAPE_TYPE_NONE;
|
||||||
|
|
||||||
// all the internals of running the particle sim
|
// all the internals of running the particle sim
|
||||||
const quint32 XYZ_STRIDE = 3;
|
QVector<float> _particleLifetimes;
|
||||||
float* _paLife;
|
QVector<glm::vec3> _particlePositions;
|
||||||
float* _paPosition;
|
QVector<glm::vec3> _particleVelocities;
|
||||||
float* _paVelocity;
|
float _timeUntilNextEmit;
|
||||||
float _partialEmit;
|
|
||||||
quint32 _paCount;
|
|
||||||
quint32 _paHead;
|
|
||||||
float _paXmin;
|
|
||||||
float _paXmax;
|
|
||||||
float _paYmin;
|
|
||||||
float _paYmax;
|
|
||||||
float _paZmin;
|
|
||||||
float _paZmax;
|
|
||||||
unsigned int _randSeed;
|
|
||||||
|
|
||||||
|
// 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
|
#endif // hifi_ParticleEffectEntityItem_h
|
||||||
|
|
|
@ -74,7 +74,7 @@ PacketVersion versionForPacketType(PacketType type) {
|
||||||
return 1;
|
return 1;
|
||||||
case PacketTypeEntityAddOrEdit:
|
case PacketTypeEntityAddOrEdit:
|
||||||
case PacketTypeEntityData:
|
case PacketTypeEntityData:
|
||||||
return VERSION_ENTITIES_ZONE_ENTITIES_HAVE_SKYBOX;
|
return VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES;
|
||||||
case PacketTypeEntityErase:
|
case PacketTypeEntityErase:
|
||||||
return 2;
|
return 2;
|
||||||
case PacketTypeAudioStreamStats:
|
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_HAVE_NAMES = 19;
|
||||||
const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_ATMOSPHERE = 20;
|
const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_ATMOSPHERE = 20;
|
||||||
const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_SKYBOX = 21;
|
const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_SKYBOX = 21;
|
||||||
|
const PacketVersion VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES = 22;
|
||||||
|
|
||||||
#endif // hifi_PacketHeaders_h
|
#endif // hifi_PacketHeaders_h
|
||||||
|
|
Loading…
Reference in a new issue