mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 06:44:06 +02:00
Merge pull request #4820 from hyperlogic/ajt/particle-system-improvements
Improvements to particle entity.
This commit is contained in:
commit
4d9e413b3f
9 changed files with 491 additions and 232 deletions
93
examples/ajt-test.js
Normal file
93
examples/ajt-test.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
|
||||
(function () {
|
||||
var spawnPoint = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
// constructor
|
||||
function TestBox() {
|
||||
this.entity = Entities.addEntity({ type: "Box",
|
||||
position: spawnPoint,
|
||||
dimentions: { x: 1, y: 1, z: 1 },
|
||||
color: { red: 100, green: 100, blue: 255 },
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
visible: true,
|
||||
locked: false,
|
||||
lifetime: 6000 });
|
||||
var self = this;
|
||||
this.timer = Script.setInterval(function () {
|
||||
var colorProp = { color: { red: Math.random() * 255,
|
||||
green: Math.random() * 255,
|
||||
blue: Math.random() * 255 } };
|
||||
Entities.editEntity(self.entity, colorProp);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
TestBox.prototype.Destroy = function () {
|
||||
Script.clearInterval(this.timer);
|
||||
Entities.editEntity(this.entity, { locked: false });
|
||||
Entities.deleteEntity(this.entity);
|
||||
}
|
||||
|
||||
// constructor
|
||||
function TestFx(color, emitDirection, emitRate, emitStrength, blinkRate) {
|
||||
var animationSettings = JSON.stringify({ fps: 30,
|
||||
frameIndex: 0,
|
||||
running: true,
|
||||
firstFrame: 0,
|
||||
lastFrame: 30,
|
||||
loop: true });
|
||||
|
||||
this.entity = Entities.addEntity({ type: "ParticleEffect",
|
||||
animationSettings: animationSettings,
|
||||
position: spawnPoint,
|
||||
textures: "http://www.hyperlogic.org/images/particle.png",
|
||||
emitRate: emitRate,
|
||||
emitStrength: emitStrength,
|
||||
emitDirection: emitDirection,
|
||||
color: color,
|
||||
visible: true,
|
||||
locked: false });
|
||||
|
||||
this.isPlaying = true;
|
||||
|
||||
var self = this;
|
||||
this.timer = Script.setInterval(function () {
|
||||
// flip is playing state
|
||||
self.isPlaying = !self.isPlaying;
|
||||
var animProp = { animationIsPlaying: self.isPlaying };
|
||||
Entities.editEntity(self.entity, animProp);
|
||||
}, (1 / blinkRate) * 1000);
|
||||
}
|
||||
|
||||
TestFx.prototype.Destroy = function () {
|
||||
Script.clearInterval(this.timer);
|
||||
Entities.editEntity(this.entity, { locked: false });
|
||||
Entities.deleteEntity(this.entity);
|
||||
}
|
||||
|
||||
var objs = [];
|
||||
function Init() {
|
||||
objs.push(new TestBox());
|
||||
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 = objs.length;
|
||||
for (i = 0; i < len; i++) {
|
||||
objs[i].Destroy();
|
||||
}
|
||||
objs = [];
|
||||
}
|
||||
|
||||
Init();
|
||||
Script.scriptEnding.connect(ShutDown);
|
||||
})();
|
||||
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
#include <DeferredLightingEffect.h>
|
||||
#include <PerfStat.h>
|
||||
#include <GeometryCache.h>
|
||||
#include "EntitiesRendererLogging.h"
|
||||
|
||||
#include "RenderableParticleEffectEntityItem.h"
|
||||
|
||||
|
@ -29,62 +30,158 @@ RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const Ent
|
|||
}
|
||||
|
||||
void RenderableParticleEffectEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render");
|
||||
|
||||
assert(getType() == EntityTypes::ParticleEffect);
|
||||
float pa_rad = getParticleRadius();
|
||||
PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render");
|
||||
|
||||
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());
|
||||
|
||||
// Right now we're just iterating over particles and rendering as a cross of four quads.
|
||||
// This is pretty dumb, it was quick enough to code up. Really, there should be many
|
||||
// rendering modes, including the all-important textured billboards.
|
||||
|
||||
QVector<glm::vec3>* pointVec = new QVector<glm::vec3>(_paCount * VERTS_PER_PARTICLE);
|
||||
quint32 paIter = _paHead;
|
||||
while (_paLife[paIter] > 0.0f) {
|
||||
int j = paIter * XYZ_STRIDE;
|
||||
|
||||
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
|
||||
|
||||
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
|
||||
pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2]));
|
||||
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] - pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] + pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] + pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] - pa_rad));
|
||||
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] + pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] - pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] - pa_rad));
|
||||
pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] + pa_rad));
|
||||
|
||||
paIter = (paIter + 1) % _maxParticles;
|
||||
if (_texturesChangedFlag) {
|
||||
if (_textures.isEmpty()) {
|
||||
_texture.clear();
|
||||
} else {
|
||||
// for now use the textures string directly.
|
||||
// Eventually we'll want multiple textures in a map or array.
|
||||
_texture = DependencyManager::get<TextureCache>()->getTexture(_textures);
|
||||
}
|
||||
_texturesChangedFlag = false;
|
||||
}
|
||||
|
||||
DependencyManager::get<GeometryCache>()->updateVertices(_cacheID, *pointVec, paColor);
|
||||
|
||||
if (!_texture) {
|
||||
renderUntexturedQuads(args);
|
||||
} else if (_texture && !_texture->isLoaded()) {
|
||||
renderUntexturedQuads(args);
|
||||
} else {
|
||||
renderTexturedQuads(args);
|
||||
}
|
||||
};
|
||||
|
||||
void RenderableParticleEffectEntityItem::renderUntexturedQuads(RenderArgs* args) {
|
||||
|
||||
float particleRadius = getParticleRadius();
|
||||
|
||||
const float MAX_COLOR = 255.0f;
|
||||
glm::vec4 particleColor(getColor()[RED_INDEX] / MAX_COLOR,
|
||||
getColor()[GREEN_INDEX] / MAX_COLOR,
|
||||
getColor()[BLUE_INDEX] / MAX_COLOR,
|
||||
getLocalRenderAlpha());
|
||||
|
||||
glm::vec3 upOffset = args->_viewFrustum->getUp() * particleRadius;
|
||||
glm::vec3 rightOffset = args->_viewFrustum->getRight() * particleRadius;
|
||||
|
||||
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];
|
||||
|
||||
// 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++;
|
||||
}
|
||||
|
||||
// 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();
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
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();
|
||||
}
|
||||
|
||||
static glm::vec3 zSortAxis;
|
||||
static bool zSort(const glm::vec3& rhs, const glm::vec3& lhs) {
|
||||
return glm::dot(rhs, ::zSortAxis) > glm::dot(lhs, ::zSortAxis);
|
||||
}
|
||||
|
||||
void RenderableParticleEffectEntityItem::renderTexturedQuads(RenderArgs* args) {
|
||||
|
||||
float particleRadius = getParticleRadius();
|
||||
|
||||
const float MAX_COLOR = 255.0f;
|
||||
glm::vec4 particleColor(getColor()[RED_INDEX] / MAX_COLOR,
|
||||
getColor()[GREEN_INDEX] / MAX_COLOR,
|
||||
getColor()[BLUE_INDEX] / MAX_COLOR,
|
||||
getLocalRenderAlpha());
|
||||
|
||||
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
|
||||
::zSortAxis = args->_viewFrustum->getDirection();
|
||||
qSort(positions.begin(), positions.end(), zSort);
|
||||
|
||||
QVector<glm::vec3> vertices(getLivingParticleCount() * VERTS_PER_PARTICLE);
|
||||
QVector<glm::vec2> textureCoords(getLivingParticleCount() * VERTS_PER_PARTICLE);
|
||||
|
||||
glm::vec3 upOffset = args->_viewFrustum->getUp() * particleRadius;
|
||||
glm::vec3 rightOffset = args->_viewFrustum->getRight() * particleRadius;
|
||||
|
||||
for (int i = 0; i < positions.size(); i++) {
|
||||
glm::vec3 pos = positions[i];
|
||||
|
||||
// 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);
|
||||
|
||||
textureCoords.append(glm::vec2(0, 1));
|
||||
textureCoords.append(glm::vec2(1, 1));
|
||||
textureCoords.append(glm::vec2(1, 0));
|
||||
textureCoords.append(glm::vec2(0, 0));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
glPushMatrix();
|
||||
|
||||
glm::vec3 positionToCenter = getCenter() - position;
|
||||
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
|
||||
|
||||
DependencyManager::get<GeometryCache>()->renderVertices(gpu::QUADS, _cacheID);
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
delete pointVec;
|
||||
};
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#define hifi_RenderableParticleEffectEntityItem_h
|
||||
|
||||
#include <ParticleEffectEntityItem.h>
|
||||
#include <TextureCache.h>
|
||||
|
||||
class RenderableParticleEffectEntityItem : public ParticleEffectEntityItem {
|
||||
public:
|
||||
|
@ -19,9 +20,15 @@ public:
|
|||
RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
|
||||
virtual void render(RenderArgs* args);
|
||||
|
||||
void renderUntexturedQuads(RenderArgs* args);
|
||||
void renderTexturedQuads(RenderArgs* args);
|
||||
|
||||
protected:
|
||||
|
||||
int _cacheID;
|
||||
const int VERTS_PER_PARTICLE = 16;
|
||||
const int VERTS_PER_PARTICLE = 4;
|
||||
|
||||
NetworkTexturePointer _texture;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -18,12 +18,8 @@
|
|||
// - Add controls for spread (which is currently hard-coded) and varying emission strength (not currently implemented).
|
||||
// - Add drag.
|
||||
// - Add some kind of support for collisions.
|
||||
// - For simplicity, I'm currently just rendering each particle as a cross of four axis-aligned quads. Really, we'd
|
||||
// want multiple render modes, including (the most important) textured billboards (always facing camera). Also, these
|
||||
// should support animated textures.
|
||||
// - There's no synchronization of the simulation across clients at all. In fact, it's using rand() under the hood, so
|
||||
// there's no gaurantee that different clients will see simulations that look anything like the other.
|
||||
// - MORE?
|
||||
//
|
||||
// Created by Jason Rickwald on 3/2/15.
|
||||
//
|
||||
|
@ -45,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;
|
||||
|
@ -55,6 +52,7 @@ const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION(0.0f, 1.0f, 0.0
|
|||
const float ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH = 25.0f;
|
||||
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) {
|
||||
|
@ -63,40 +61,40 @@ EntityItem* ParticleEffectEntityItem::factory(const EntityItemID& entityID, cons
|
|||
|
||||
// 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;
|
||||
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 {
|
||||
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
|
||||
|
||||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationIsPlaying, getAnimationIsPlaying);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFrameIndex, getAnimationFrameIndex);
|
||||
|
@ -111,6 +109,7 @@ EntityItemProperties ParticleEffectEntityItem::getProperties() const {
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitStrength, getEmitStrength);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(localGravity, getLocalGravity);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleRadius, getParticleRadius);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
@ -132,6 +131,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitStrength, setEmitStrength);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(localGravity, setLocalGravity);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleRadius, setParticleRadius);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
|
@ -147,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;
|
||||
|
@ -186,6 +186,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
|
|||
READ_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, float, _emitStrength);
|
||||
READ_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, float, _localGravity);
|
||||
READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, _particleRadius);
|
||||
READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, _textures);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
@ -194,7 +195,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
|
|||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
|
||||
|
||||
requestedProperties += PROP_COLOR;
|
||||
requestedProperties += PROP_ANIMATION_FPS;
|
||||
requestedProperties += PROP_ANIMATION_FRAME_INDEX;
|
||||
|
@ -208,17 +209,18 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea
|
|||
requestedProperties += PROP_EMIT_STRENGTH;
|
||||
requestedProperties += PROP_LOCAL_GRAVITY;
|
||||
requestedProperties += PROP_PARTICLE_RADIUS;
|
||||
requestedProperties += PROP_TEXTURES;
|
||||
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
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());
|
||||
|
@ -234,11 +236,12 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData,
|
|||
APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, appendValue, getEmitStrength());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, getLocalGravity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, getParticleRadius());
|
||||
APPEND_ENTITY_PROPERTY(PROP_TEXTURES, appendValue, getTextures());
|
||||
}
|
||||
|
||||
bool ParticleEffectEntityItem::isAnimatingSomething() const {
|
||||
return getAnimationIsPlaying() &&
|
||||
getAnimationFPS() != 0.0f;
|
||||
// keep animating if there are particles still alive.
|
||||
return (getAnimationIsPlaying() || getLivingParticleCount() > 0) && getAnimationFPS() != 0.0f;
|
||||
}
|
||||
|
||||
bool ParticleEffectEntityItem::needsToCallUpdate() const {
|
||||
|
@ -246,33 +249,25 @@ bool ParticleEffectEntityItem::needsToCallUpdate() const {
|
|||
}
|
||||
|
||||
void ParticleEffectEntityItem::update(const quint64& now) {
|
||||
|
||||
float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND;
|
||||
_lastAnimated = now;
|
||||
|
||||
// only advance the frame index if we're playing
|
||||
if (getAnimationIsPlaying()) {
|
||||
float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND;
|
||||
_lastAnimated = now;
|
||||
float lastFrame = _animationLoop.getFrameIndex();
|
||||
_animationLoop.simulate(deltaTime);
|
||||
float curFrame = _animationLoop.getFrameIndex();
|
||||
if (curFrame > lastFrame) {
|
||||
stepSimulation(deltaTime);
|
||||
}
|
||||
else if (curFrame < lastFrame) {
|
||||
// we looped around, so restart the sim and only sim up to the point
|
||||
// since the beginning of the frame range.
|
||||
resetSimulation();
|
||||
stepSimulation((curFrame - _animationLoop.getFirstFrame()) / _animationLoop.getFPS());
|
||||
}
|
||||
}
|
||||
else {
|
||||
_lastAnimated = 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;
|
||||
setDimensions(dims);
|
||||
if (isAnimatingSomething()) {
|
||||
stepSimulation(deltaTime);
|
||||
|
||||
// update the dimensions
|
||||
glm::vec3 dims;
|
||||
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);
|
||||
}
|
||||
|
||||
EntityItem::update(now); // let our base class handle it's updates...
|
||||
}
|
||||
|
@ -418,96 +413,103 @@ QString ParticleEffectEntityItem::getAnimationSettings() const {
|
|||
return jsonByteString;
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::stepSimulation(float deltaTime) {
|
||||
_paXmin = _paYmin = _paZmin = -1.0f;
|
||||
_paXmax = _paYmax = _paZmax = 1.0f;
|
||||
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);
|
||||
}
|
||||
|
||||
// update particles
|
||||
quint32 updateIter = _paHead;
|
||||
while (_paLife[updateIter] > 0.0f) {
|
||||
_paLife[updateIter] -= deltaTime;
|
||||
if (_paLife[updateIter] <= 0.0f) {
|
||||
_paLife[updateIter] = -1.0f;
|
||||
_paHead = (_paHead + 1) % _maxParticles;
|
||||
_paCount--;
|
||||
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) {
|
||||
|
||||
_particleMinBound = glm::vec3(-1.0f, -1.0f, -1.0f);
|
||||
_particleMaxBound = glm::vec3(1.0f, 1.0f, 1.0f);
|
||||
|
||||
// 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 {
|
||||
// DUMB FORWARD EULER just to get it done
|
||||
int j = updateIter * XYZ_STRIDE;
|
||||
_paPosition[j] += _paVelocity[j] * deltaTime;
|
||||
_paPosition[j+1] += _paVelocity[j+1] * deltaTime;
|
||||
_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;
|
||||
}
|
||||
|
||||
// emit new particles
|
||||
quint32 emitIdx = updateIter;
|
||||
_partialEmit += ((float)_emitRate) * deltaTime;
|
||||
quint32 birthed = (quint32)_partialEmit;
|
||||
_partialEmit -= (float)birthed;
|
||||
glm::vec3 randOffset;
|
||||
// emit new particles, but only if animaiton is playing
|
||||
if (getAnimationIsPlaying()) {
|
||||
|
||||
for (quint32 i = 0; i < birthed; i++) {
|
||||
if (_paLife[emitIdx] < 0.0f) {
|
||||
int j = emitIdx * XYZ_STRIDE;
|
||||
_paLife[emitIdx] = _lifespan;
|
||||
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;
|
||||
_paVelocity[j] = (_emitDirection.x * _emitStrength) + randOffset.x;
|
||||
_paVelocity[j + 1] = (_emitDirection.y * _emitStrength) + randOffset.y;
|
||||
_paVelocity[j + 2] = (_emitDirection.z * _emitStrength) + randOffset.z;
|
||||
float timeLeftInFrame = deltaTime;
|
||||
while (_timeUntilNextEmit < timeLeftInFrame) {
|
||||
|
||||
// DUMB FORWARD EULER just to get it done
|
||||
_paPosition[j] += _paVelocity[j] * deltaTime;
|
||||
_paPosition[j + 1] += _paVelocity[j + 1] * deltaTime;
|
||||
_paPosition[j + 2] += _paVelocity[j + 2] * deltaTime;
|
||||
timeLeftInFrame -= _timeUntilNextEmit;
|
||||
_timeUntilNextEmit = 1.0f / _emitRate;
|
||||
|
||||
_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]);
|
||||
// emit a new particle at tail index.
|
||||
quint32 i = _particleTailIndex;
|
||||
_particleLifetimes[i] = _lifespan;
|
||||
|
||||
// massless particles
|
||||
// and simple gravity down
|
||||
_paVelocity[j + 1] += deltaTime * _localGravity;
|
||||
// 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;
|
||||
|
||||
emitIdx = (emitIdx + 1) % _maxParticles;
|
||||
_paCount++;
|
||||
// set initial conditions
|
||||
_particlePositions[i] = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
_particleVelocities[i] = _emitDirection * _emitStrength + randOffset;
|
||||
|
||||
integrateParticle(i, timeLeftInFrame);
|
||||
extendBounds(_particlePositions[i]);
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,29 +2,6 @@
|
|||
// ParticleEffectEntityItem.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Some starter code for a particle simulation entity, which could ideally be used for a variety of effects.
|
||||
// This is some really early and rough stuff here. It was enough for now to just get it up and running in the interface.
|
||||
//
|
||||
// Todo's and other notes:
|
||||
// - The simulation should restart when the AnimationLoop's max frame is reached (or passed), but there doesn't seem
|
||||
// to be a good way to set that max frame to something reasonable right now.
|
||||
// - There seems to be a bug whereby entities on the edge of screen will just pop off or on. This is probably due
|
||||
// to my lack of understanding of how entities in the octree are picked for rendering. I am updating the entity
|
||||
// dimensions based on the bounds of the sim, but maybe I need to update a dirty flag or something.
|
||||
// - This should support some kind of pre-roll of the simulation.
|
||||
// - Just to get this out the door, I just did forward Euler integration. There are better ways.
|
||||
// - Gravity always points along the Y axis. Support an actual gravity vector.
|
||||
// - Add the ability to add arbitrary forces to the simulation.
|
||||
// - Add controls for spread (which is currently hard-coded) and varying emission strength (not currently implemented).
|
||||
// - Add drag.
|
||||
// - Add some kind of support for collisions.
|
||||
// - For simplicity, I'm currently just rendering each particle as a cross of four axis-aligned quads. Really, we'd
|
||||
// want multiple render modes, including (the most important) textured billboards (always facing camera). Also, these
|
||||
// should support animated textures.
|
||||
// - There's no synchronization of the simulation across clients at all. In fact, it's using rand() under the hood, so
|
||||
// there's no gaurantee that different clients will see simulations that look anything like the other.
|
||||
// - MORE?
|
||||
//
|
||||
// Created by Jason Rickwald on 3/2/15.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
@ -39,14 +16,14 @@
|
|||
|
||||
class ParticleEffectEntityItem : public EntityItem {
|
||||
public:
|
||||
|
||||
|
||||
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
|
||||
ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
|
||||
virtual ~ParticleEffectEntityItem();
|
||||
|
||||
ALLOW_INSTANTIATION // This class can be instantiated
|
||||
|
||||
|
||||
// methods for getting/setting all properties of this entity
|
||||
virtual EntityItemProperties getProperties() const;
|
||||
virtual bool setProperties(const EntityItemProperties& properties);
|
||||
|
@ -54,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;
|
||||
|
@ -71,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;
|
||||
|
@ -109,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;
|
||||
|
@ -121,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;
|
||||
|
@ -141,11 +119,22 @@ public:
|
|||
float getAnimationFPS() const { return _animationLoop.getFPS(); }
|
||||
QString getAnimationSettings() const;
|
||||
|
||||
static const QString DEFAULT_TEXTURES;
|
||||
const QString& getTextures() const { return _textures; }
|
||||
void setTextures(const QString& textures) {
|
||||
if (_textures != textures) {
|
||||
_textures = textures;
|
||||
_texturesChangedFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -159,25 +148,24 @@ protected:
|
|||
quint64 _lastAnimated;
|
||||
AnimationLoop _animationLoop;
|
||||
QString _animationSettings;
|
||||
QString _textures;
|
||||
bool _texturesChangedFlag;
|
||||
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
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
return 1;
|
||||
case PacketTypeEntityAddOrEdit:
|
||||
case PacketTypeEntityData:
|
||||
return VERSION_ENTITIES_ZONE_ENTITIES_STAGE_HAS_AUTOMATIC_HOURDAY;
|
||||
return VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES;
|
||||
case PacketTypeEntityErase:
|
||||
return 2;
|
||||
case PacketTypeAudioStreamStats:
|
||||
|
|
|
@ -173,5 +173,6 @@ 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_ZONE_ENTITIES_STAGE_HAS_AUTOMATIC_HOURDAY = 22;
|
||||
const PacketVersion VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES = 23;
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -721,6 +721,76 @@ void GeometryCache::updateVertices(int id, const QVector<glm::vec3>& points, con
|
|||
#endif
|
||||
}
|
||||
|
||||
void GeometryCache::updateVertices(int id, const QVector<glm::vec3>& points, const QVector<glm::vec2>& texCoords, const glm::vec4& color) {
|
||||
BatchItemDetails& details = _registeredVertices[id];
|
||||
|
||||
if (details.isCreated) {
|
||||
details.clear();
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(renderutils) << "updateVertices()... RELEASING REGISTERED";
|
||||
#endif // def WANT_DEBUG
|
||||
}
|
||||
|
||||
const int FLOATS_PER_VERTEX = 5;
|
||||
details.isCreated = true;
|
||||
details.vertices = points.size();
|
||||
details.vertexSize = FLOATS_PER_VERTEX;
|
||||
|
||||
gpu::BufferPointer verticesBuffer(new gpu::Buffer());
|
||||
gpu::BufferPointer colorBuffer(new gpu::Buffer());
|
||||
gpu::Stream::FormatPointer streamFormat(new gpu::Stream::Format());
|
||||
gpu::BufferStreamPointer stream(new gpu::BufferStream());
|
||||
|
||||
details.verticesBuffer = verticesBuffer;
|
||||
details.colorBuffer = colorBuffer;
|
||||
details.streamFormat = streamFormat;
|
||||
details.stream = stream;
|
||||
|
||||
details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
|
||||
details.streamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), 3 * sizeof(float));
|
||||
details.streamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA));
|
||||
|
||||
details.stream->addBuffer(details.verticesBuffer, 0, details.streamFormat->getChannels().at(0)._stride);
|
||||
details.stream->addBuffer(details.colorBuffer, 0, details.streamFormat->getChannels().at(1)._stride);
|
||||
|
||||
assert(points.size() == texCoords.size());
|
||||
|
||||
details.vertices = points.size();
|
||||
details.vertexSize = FLOATS_PER_VERTEX;
|
||||
|
||||
int compactColor = ((int(color.x * 255.0f) & 0xFF)) |
|
||||
((int(color.y * 255.0f) & 0xFF) << 8) |
|
||||
((int(color.z * 255.0f) & 0xFF) << 16) |
|
||||
((int(color.w * 255.0f) & 0xFF) << 24);
|
||||
|
||||
GLfloat* vertexData = new GLfloat[details.vertices * FLOATS_PER_VERTEX];
|
||||
GLfloat* vertex = vertexData;
|
||||
|
||||
int* colorData = new int[details.vertices];
|
||||
int* colorDataAt = colorData;
|
||||
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
glm::vec3 point = points[i];
|
||||
glm::vec2 texCoord = texCoords[i];
|
||||
*(vertex++) = point.x;
|
||||
*(vertex++) = point.y;
|
||||
*(vertex++) = point.z;
|
||||
*(vertex++) = texCoord.x;
|
||||
*(vertex++) = texCoord.y;
|
||||
|
||||
*(colorDataAt++) = compactColor;
|
||||
}
|
||||
|
||||
details.verticesBuffer->append(sizeof(GLfloat) * FLOATS_PER_VERTEX * details.vertices, (gpu::Byte*) vertexData);
|
||||
details.colorBuffer->append(sizeof(int) * details.vertices, (gpu::Byte*) colorData);
|
||||
delete[] vertexData;
|
||||
delete[] colorData;
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(renderutils) << "new registered linestrip buffer made -- _registeredVertices.size():" << _registeredVertices.size();
|
||||
#endif
|
||||
}
|
||||
|
||||
void GeometryCache::renderVertices(gpu::Primitive primitiveType, int id) {
|
||||
BatchItemDetails& details = _registeredVertices[id];
|
||||
if (details.isCreated) {
|
||||
|
|
|
@ -198,6 +198,7 @@ public:
|
|||
|
||||
void updateVertices(int id, const QVector<glm::vec2>& points, const glm::vec4& color);
|
||||
void updateVertices(int id, const QVector<glm::vec3>& points, const glm::vec4& color);
|
||||
void updateVertices(int id, const QVector<glm::vec3>& points, const QVector<glm::vec2>& texCoords, const glm::vec4& color);
|
||||
void renderVertices(gpu::Primitive primitiveType, int id);
|
||||
|
||||
/// Loads geometry from the specified URL.
|
||||
|
|
Loading…
Reference in a new issue