From 3522357c8c10fba5866aaecec408b7db61cda230 Mon Sep 17 00:00:00 2001 From: Jason <2billbrasky@gmail.com> Date: Wed, 4 Mar 2015 16:06:06 -0800 Subject: [PATCH] High Fidelity interview project -- Jason Rickwald For my project, I decided to add a new entity -- a Particle Effect. This is really rudimentary to start out with, but you could see where it's headed. --- .../src/EntityTreeRenderer.cpp | 2 + .../RenderableParticleEffectEntityItem.cpp | 88 +++ .../src/RenderableParticleEffectEntityItem.h | 29 + .../entities/src/EntityItemProperties.cpp | 57 ++ libraries/entities/src/EntityItemProperties.h | 26 +- libraries/entities/src/EntityTypes.cpp | 2 + libraries/entities/src/EntityTypes.h | 3 +- .../entities/src/ParticleEffectEntityItem.cpp | 522 ++++++++++++++++++ .../entities/src/ParticleEffectEntityItem.h | 181 ++++++ 9 files changed, 908 insertions(+), 2 deletions(-) create mode 100644 libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp create mode 100644 libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h create mode 100644 libraries/entities/src/ParticleEffectEntityItem.cpp create mode 100644 libraries/entities/src/ParticleEffectEntityItem.h diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 46f9ff6f55..3db4ffb2ac 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -32,6 +32,7 @@ #include "RenderableModelEntityItem.h" #include "RenderableSphereEntityItem.h" #include "RenderableTextEntityItem.h" +#include "RenderableParticleEffectEntityItem.h" EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, @@ -53,6 +54,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory) + REGISTER_ENTITY_TYPE_WITH_FACTORY(ParticleEffect, RenderableParticleEffectEntityItem::factory) _currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID _currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp new file mode 100644 index 0000000000..a10f59287c --- /dev/null +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -0,0 +1,88 @@ +// +// RenderableParticleEffectEntityItem.cpp +// interface/src +// +// Created by Jason Rickwald on 3/2/15. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include + +#include +#include +#include + +#include "RenderableParticleEffectEntityItem.h" + +EntityItem* RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return new RenderableParticleEffectEntityItem(entityID, properties); +} + +void RenderableParticleEffectEntityItem::render(RenderArgs* args) { + PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render"); + assert(getType() == EntityTypes::ParticleEffect); + glm::vec3 position = getPositionInMeters(); + glm::vec3 center = getCenterInMeters(); + glm::quat rotation = getRotation(); + float pa_rad = getParticleRadius(); + + const float MAX_COLOR = 255.0f; + glm::vec4 sphereColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, + getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); + + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + + + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + + const int SLICES = 8; + const int STACKS = 5; + + glBegin(GL_QUADS); + glColor4f(sphereColor.r, sphereColor.g, sphereColor.b, sphereColor.a); + + // 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. + + quint32 paiter = pa_head; + while (pa_life[paiter] > 0.0f) + { + int j = paiter * 3; + + glVertex3f(pa_position[j] - pa_rad, pa_position[j + 1] + pa_rad, pa_position[j + 2]); + glVertex3f(pa_position[j] + pa_rad, pa_position[j + 1] + pa_rad, pa_position[j + 2]); + glVertex3f(pa_position[j] + pa_rad, pa_position[j + 1] - pa_rad, pa_position[j + 2]); + glVertex3f(pa_position[j] - pa_rad, pa_position[j + 1] - pa_rad, pa_position[j + 2]); + + glVertex3f(pa_position[j] + pa_rad, pa_position[j + 1] + pa_rad, pa_position[j + 2]); + glVertex3f(pa_position[j] - pa_rad, pa_position[j + 1] + pa_rad, pa_position[j + 2]); + glVertex3f(pa_position[j] - pa_rad, pa_position[j + 1] - pa_rad, pa_position[j + 2]); + glVertex3f(pa_position[j] + pa_rad, pa_position[j + 1] - pa_rad, pa_position[j + 2]); + + glVertex3f(pa_position[j], pa_position[j + 1] + pa_rad, pa_position[j + 2] - pa_rad); + glVertex3f(pa_position[j], pa_position[j + 1] + pa_rad, pa_position[j + 2] + pa_rad); + glVertex3f(pa_position[j], pa_position[j + 1] - pa_rad, pa_position[j + 2] + pa_rad); + glVertex3f(pa_position[j], pa_position[j + 1] - pa_rad, pa_position[j + 2] - pa_rad); + + glVertex3f(pa_position[j], pa_position[j + 1] + pa_rad, pa_position[j + 2] + pa_rad); + glVertex3f(pa_position[j], pa_position[j + 1] + pa_rad, pa_position[j + 2] - pa_rad); + glVertex3f(pa_position[j], pa_position[j + 1] - pa_rad, pa_position[j + 2] - pa_rad); + glVertex3f(pa_position[j], pa_position[j + 1] - pa_rad, pa_position[j + 2] + pa_rad); + + paiter = (paiter + 1) % _maxParticles; + } + + glEnd(); + glPopMatrix(); + glPopMatrix(); +}; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h new file mode 100644 index 0000000000..837c878a45 --- /dev/null +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -0,0 +1,29 @@ +// +// RenderableParticleEffectEntityItem.h +// interface/src/entities +// +// Created by Jason Rickwald on 3/2/15. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RenderableParticleEffectEntityItem_h +#define hifi_RenderableParticleEffectEntityItem_h + +#include + +class RenderableParticleEffectEntityItem : public ParticleEffectEntityItem { +public: + static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : + ParticleEffectEntityItem(entityItemID, properties) + { } + + virtual void render(RenderArgs* args); + +}; + + +#endif // hifi_RenderableParticleEffectEntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index f3f84876ba..87b59b0392 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -23,6 +23,7 @@ #include "EntityItemPropertiesDefaults.h" #include "ModelEntityItem.h" #include "TextEntityItem.h" +#include "ParticleEffectEntityItem.h" EntityItemProperties::EntityItemProperties() : @@ -66,6 +67,13 @@ EntityItemProperties::EntityItemProperties() : CONSTRUCT_PROPERTY(textColor, TextEntityItem::DEFAULT_TEXT_COLOR), CONSTRUCT_PROPERTY(backgroundColor, TextEntityItem::DEFAULT_BACKGROUND_COLOR), CONSTRUCT_PROPERTY(shapeType, SHAPE_TYPE_NONE), + CONSTRUCT_PROPERTY(maxParticles, ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES), + CONSTRUCT_PROPERTY(lifespan, ParticleEffectEntityItem::DEFAULT_LIFESPAN), + CONSTRUCT_PROPERTY(emitRate, ParticleEffectEntityItem::DEFAULT_EMIT_RATE), + CONSTRUCT_PROPERTY(emitDirection, ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION), + CONSTRUCT_PROPERTY(emitStrength, ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH), + CONSTRUCT_PROPERTY(localGravity, ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY), + CONSTRUCT_PROPERTY(particleRadius, ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS), _id(UNKNOWN_ENTITY_ID), _idSet(false), @@ -238,6 +246,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_TEXT_COLOR, textColor); CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_COLOR, backgroundColor); CHECK_PROPERTY_CHANGE(PROP_SHAPE_TYPE, shapeType); + CHECK_PROPERTY_CHANGE(PROP_MAX_PARTICLES, maxParticles); + CHECK_PROPERTY_CHANGE(PROP_LIFESPAN, lifespan); + CHECK_PROPERTY_CHANGE(PROP_EMIT_RATE, emitRate); + CHECK_PROPERTY_CHANGE(PROP_EMIT_DIRECTION, emitDirection); + CHECK_PROPERTY_CHANGE(PROP_EMIT_STRENGTH, emitStrength); + CHECK_PROPERTY_CHANGE(PROP_LOCAL_GRAVITY, localGravity); + CHECK_PROPERTY_CHANGE(PROP_PARTICLE_RADIUS, particleRadius); return changedProperties; } @@ -297,6 +312,13 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(textColor, getTextColor()); COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(backgroundColor, getBackgroundColor()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(shapeType, getShapeTypeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(maxParticles); + COPY_PROPERTY_TO_QSCRIPTVALUE(lifespan); + COPY_PROPERTY_TO_QSCRIPTVALUE(emitRate); + COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(emitDirection); + COPY_PROPERTY_TO_QSCRIPTVALUE(emitStrength); + COPY_PROPERTY_TO_QSCRIPTVALUE(localGravity); + COPY_PROPERTY_TO_QSCRIPTVALUE(particleRadius); // Sitting properties support QScriptValue sittingPoints = engine->newObject(); @@ -375,6 +397,13 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(textColor, setTextColor); COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(backgroundColor, setBackgroundColor); COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(shapeType, ShapeType); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(maxParticles, setMaxParticles); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(lifespan, setLifespan); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(emitRate, setEmitRate); + COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(emitDirection, setEmitDirection); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(emitStrength, setEmitStrength); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localGravity, setLocalGravity); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(particleRadius, setParticleRadius); _lastEdited = usecTimestampNow(); } @@ -552,6 +581,16 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_EXPONENT, appendValue, properties.getExponent()); APPEND_ENTITY_PROPERTY(PROP_CUTOFF, appendValue, properties.getCutoff()); } + + if (properties.getType() == EntityTypes::ParticleEffect) { + APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, appendValue, properties.getMaxParticles()); + APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, appendValue, properties.getLifespan()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, appendValue, properties.getEmitRate()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, appendValue, properties.getEmitDirection()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, appendValue, properties.getEmitStrength()); + APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, properties.getLocalGravity()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, properties.getParticleRadius()); + } } if (propertyCount > 0) { int endOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -774,6 +813,16 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff); } + + if (properties.getType() == EntityTypes::ParticleEffect) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_PARTICLES, float, setMaxParticles); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFESPAN, float, setLifespan); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_RATE, float, setEmitRate); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_DIRECTION, glm::vec3, setEmitDirection); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_STRENGTH, float, setEmitStrength); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCAL_GRAVITY, float, setLocalGravity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_RADIUS, float, setParticleRadius); + } return valid; } @@ -851,6 +900,14 @@ void EntityItemProperties::markAllChanged() { _textColorChanged = true; _backgroundColorChanged = true; _shapeTypeChanged = true; + + _maxParticlesChanged = true; + _lifespanChanged = true; + _emitRateChanged = true; + _emitDirectionChanged = true; + _emitStrengthChanged = true; + _localGravityChanged = true; + _particleRadiusChanged = true; } AACube EntityItemProperties::getMaximumAACubeInTreeUnits() const { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 2391bcde84..d5a3c32809 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -83,9 +83,18 @@ enum EntityPropertyList { PROP_ANIMATION_SETTINGS, PROP_USER_DATA, PROP_SHAPE_TYPE, + + // used by ParticleEffect entities + PROP_MAX_PARTICLES, + PROP_LIFESPAN, + PROP_EMIT_RATE, + PROP_EMIT_DIRECTION, + PROP_EMIT_STRENGTH, + PROP_LOCAL_GRAVITY, + PROP_PARTICLE_RADIUS, // NOTE: add new properties ABOVE this line and then modify PROP_LAST_ITEM below - PROP_LAST_ITEM = PROP_SHAPE_TYPE, + PROP_LAST_ITEM = PROP_PARTICLE_RADIUS, // These properties of TextEntity piggy back off of properties of ModelEntities, the type doesn't matter // since the derived class knows how to interpret it's own properties and knows the types it expects @@ -110,6 +119,7 @@ class EntityItemProperties { friend class SphereEntityItem; // TODO: consider removing this friend relationship and use public methods friend class LightEntityItem; // TODO: consider removing this friend relationship and use public methods friend class TextEntityItem; // TODO: consider removing this friend relationship and use public methods + friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods public: EntityItemProperties(); virtual ~EntityItemProperties(); @@ -182,6 +192,13 @@ public: DEFINE_PROPERTY_REF(PROP_TEXT_COLOR, TextColor, textColor, xColor); DEFINE_PROPERTY_REF(PROP_BACKGROUND_COLOR, BackgroundColor, backgroundColor, xColor); DEFINE_PROPERTY_REF_ENUM(PROP_SHAPE_TYPE, ShapeType, shapeType, ShapeType); + DEFINE_PROPERTY(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32); + DEFINE_PROPERTY(PROP_LIFESPAN, Lifespan, lifespan, float); + DEFINE_PROPERTY(PROP_EMIT_RATE, EmitRate, emitRate, float); + DEFINE_PROPERTY_REF(PROP_EMIT_DIRECTION, EmitDirection, emitDirection, glm::vec3); + DEFINE_PROPERTY(PROP_EMIT_STRENGTH, EmitStrength, emitStrength, float); + DEFINE_PROPERTY(PROP_LOCAL_GRAVITY, LocalGravity, localGravity, float); + DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float); public: float getMaxDimension() const { return glm::max(_dimensions.x, _dimensions.y, _dimensions.z); } @@ -304,6 +321,13 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, TextColor, textColor, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundColor, backgroundColor, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, ShapeType, shapeType, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, MaxParticles, maxParticles, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, Lifespan, lifespan, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitRate, emitRate, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitDirection, emitDirection, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitStrength, emitStrength, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalGravity, localGravity, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParticleRadius, particleRadius, ""); debug << " last edited:" << properties.getLastEdited() << "\n"; debug << " edited ago:" << properties.getEditedAgo() << "\n"; diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index fd9484e0d6..d3ffc4247c 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -23,6 +23,7 @@ #include "ModelEntityItem.h" #include "SphereEntityItem.h" #include "TextEntityItem.h" +#include "ParticleEffectEntityItem.h" QMap EntityTypes::_typeToNameMap; QMap EntityTypes::_nameToTypeMap; @@ -37,6 +38,7 @@ REGISTER_ENTITY_TYPE(Box) REGISTER_ENTITY_TYPE(Sphere) REGISTER_ENTITY_TYPE(Light) REGISTER_ENTITY_TYPE(Text) +REGISTER_ENTITY_TYPE(ParticleEffect) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 8ed407f11d..e1f8e876bb 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -35,7 +35,8 @@ public: Sphere, Light, Text, - LAST = Text + ParticleEffect, + LAST = ParticleEffect } EntityType; static const QString& getEntityTypeName(EntityType entityType); diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp new file mode 100644 index 0000000000..cf84f7be75 --- /dev/null +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -0,0 +1,522 @@ +// +// ParticleEffectEntityItem.cpp +// 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. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include +#include + +#include + +#include +#include + +#include "EntityTree.h" +#include "EntityTreeElement.h" +#include "ParticleEffectEntityItem.h" + +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; +const quint32 ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES = 1000; +const float ParticleEffectEntityItem::DEFAULT_LIFESPAN = 3.0f; +const float ParticleEffectEntityItem::DEFAULT_EMIT_RATE = 15.0f; +const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION(0.0f, 1.0f, 0.0f); +const float ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH = 25.0f; +const float ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY = -9.8f; +const float ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS = 0.025f; + + +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) +{ + _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; + 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. + pa_life = (float*) malloc(sizeof(float) * _maxParticles); + pa_position = (float*)malloc(sizeof(float) * _maxParticles * 3); // x,y,z + pa_velocity = (float*)malloc(sizeof(float) * _maxParticles * 3); // x,y,z + pa_xmax = pa_ymax = pa_zmax = 1.0f; + pa_xmin = pa_ymin = pa_zmin = -1.0f; + resetSim(); + _lastAnimated = usecTimestampNow(); +} + +ParticleEffectEntityItem::~ParticleEffectEntityItem() +{ + free(pa_life); + free(pa_position); + free(pa_velocity); +} + +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); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFPS, getAnimationFPS); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationSettings, getAnimationSettings); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxParticles, getMaxParticles); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifespan, getLifespan); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitRate, getEmitRate); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitDirection, getEmitDirection); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitStrength, getEmitStrength); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(localGravity, getLocalGravity); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleRadius, getParticleRadius); + + return properties; +} + +bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationIsPlaying, setAnimationIsPlaying); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFrameIndex, setAnimationFrameIndex); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFPS, setAnimationFPS); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationSettings, setAnimationSettings); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitRate, setEmitRate); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitDirection, setEmitDirection); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitStrength, setEmitStrength); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(localGravity, setLocalGravity); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleRadius, setParticleRadius); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qDebug() << "ParticleEffectEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color); + + // Because we're using AnimationLoop which will reset the frame index if you change it's running state + // we want to read these values in the order they appear in the buffer, but call our setters in an + // order that allows AnimationLoop to preserve the correct frame rate. + float animationFPS = getAnimationFPS(); + float animationFrameIndex = getAnimationFrameIndex(); + bool animationIsPlaying = getAnimationIsPlaying(); + READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, animationFPS); + READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, animationFrameIndex); + READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, animationIsPlaying); + + if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) { + if (animationIsPlaying != getAnimationIsPlaying()) { + setAnimationIsPlaying(animationIsPlaying); + } + } + if (propertyFlags.getHasProperty(PROP_ANIMATION_FPS)) { + setAnimationFPS(animationFPS); + } + if (propertyFlags.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) { + setAnimationFrameIndex(animationFrameIndex); + } + + READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_SETTINGS, setAnimationSettings); + READ_ENTITY_PROPERTY_SETTER(PROP_SHAPE_TYPE, ShapeType, updateShapeType); + READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, _maxParticles); + READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, _lifespan); + READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, _emitRate); + READ_ENTITY_PROPERTY_SETTER(PROP_EMIT_DIRECTION, glm::vec3, setEmitDirection); + READ_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, float, _emitStrength); + READ_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, float, _localGravity); + READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, _particleRadius); + + return bytesRead; +} + + +// 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; + requestedProperties += PROP_ANIMATION_PLAYING; + requestedProperties += PROP_ANIMATION_SETTINGS; + requestedProperties += PROP_SHAPE_TYPE; + requestedProperties += PROP_MAX_PARTICLES; + requestedProperties += PROP_LIFESPAN; + requestedProperties += PROP_EMIT_RATE; + requestedProperties += PROP_EMIT_DIRECTION; + requestedProperties += PROP_EMIT_STRENGTH; + requestedProperties += PROP_LOCAL_GRAVITY; + requestedProperties += PROP_PARTICLE_RADIUS; + + return requestedProperties; +} + +void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, getAnimationFPS()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, getAnimationFrameIndex()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, appendValue, getAnimationIsPlaying()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_SETTINGS, appendValue, getAnimationSettings()); + APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, appendValue, (uint32_t)getShapeType()); + APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, appendValue, getMaxParticles()); + APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, appendValue, getLifespan()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, appendValue, getEmitRate()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, appendValue, getEmitDirection()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, appendValue, getEmitStrength()); + APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, getLocalGravity()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, getParticleRadius()); +} + +bool ParticleEffectEntityItem::isAnimatingSomething() const { + return getAnimationIsPlaying() && + getAnimationFPS() != 0.0f; +} + +bool ParticleEffectEntityItem::needsToCallUpdate() const { + return isAnimatingSomething() ? true : EntityItem::needsToCallUpdate(); +} + +void ParticleEffectEntityItem::update(const quint64& 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) + { + stepSim(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. + resetSim(); + stepSim((curFrame - _animationLoop.getFirstFrame()) / _animationLoop.getFPS()); + } + } + else { + _lastAnimated = now; + } + + // update the dimensions + glm::vec3 dims; + dims.x = glm::max(glm::abs(pa_xmin), glm::abs(pa_xmax)); + dims.y = glm::max(glm::abs(pa_xmin), glm::abs(pa_xmax)); + dims.z = glm::max(glm::abs(pa_xmin), glm::abs(pa_xmax)); + setDimensionsInMeters(dims); + + EntityItem::update(now); // let our base class handle it's updates... +} + +void ParticleEffectEntityItem::debugDump() const { + quint64 now = usecTimestampNow(); + qDebug() << "PA EFFECT EntityItem id:" << getEntityItemID() << "---------------------------------------------"; + qDebug() << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; + qDebug() << " position:" << debugTreeVector(_position); + qDebug() << " dimensions:" << debugTreeVector(_dimensions); + qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now); +} + +void ParticleEffectEntityItem::updateShapeType(ShapeType type) { + if (type != _shapeType) { + _shapeType = type; + _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; + } +} + +void ParticleEffectEntityItem::setAnimationFrameIndex(float value) { +#ifdef WANT_DEBUG + if (isAnimatingSomething()) { + qDebug() << "ParticleEffectEntityItem::setAnimationFrameIndex()"; + qDebug() << " value:" << value; + qDebug() << " was:" << _animationLoop.getFrameIndex(); + } +#endif + _animationLoop.setFrameIndex(value); +} + +void ParticleEffectEntityItem::setAnimationSettings(const QString& value) { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + if (settingsMap.contains("fps")) { + float fps = settingsMap["fps"].toFloat(); + setAnimationFPS(fps); + } + + if (settingsMap.contains("frameIndex")) { + float frameIndex = settingsMap["frameIndex"].toFloat(); +#ifdef WANT_DEBUG + if (isAnimatingSomething()) { + qDebug() << "ParticleEffectEntityItem::setAnimationSettings() calling setAnimationFrameIndex()..."; + qDebug() << " settings:" << value; + qDebug() << " settingsMap[frameIndex]:" << settingsMap["frameIndex"]; + qDebug(" frameIndex: %20.5f", frameIndex); + } +#endif + + setAnimationFrameIndex(frameIndex); + } + + if (settingsMap.contains("running")) { + bool running = settingsMap["running"].toBool(); + if (running != getAnimationIsPlaying()) { + setAnimationIsPlaying(running); + } + } + + if (settingsMap.contains("firstFrame")) { + float firstFrame = settingsMap["firstFrame"].toFloat(); + setAnimationFirstFrame(firstFrame); + } + + if (settingsMap.contains("lastFrame")) { + float lastFrame = settingsMap["lastFrame"].toFloat(); + setAnimationLastFrame(lastFrame); + } + + if (settingsMap.contains("loop")) { + bool loop = settingsMap["loop"].toBool(); + setAnimationLoop(loop); + } + + if (settingsMap.contains("hold")) { + bool hold = settingsMap["hold"].toBool(); + setAnimationHold(hold); + } + + if (settingsMap.contains("startAutomatically")) { + bool startAutomatically = settingsMap["startAutomatically"].toBool(); + setAnimationStartAutomatically(startAutomatically); + } + + _animationSettings = value; + _dirtyFlags |= EntityItem::DIRTY_UPDATEABLE; +} + +void ParticleEffectEntityItem::setAnimationIsPlaying(bool value) { + _dirtyFlags |= EntityItem::DIRTY_UPDATEABLE; + _animationLoop.setRunning(value); +} + +void ParticleEffectEntityItem::setAnimationFPS(float value) { + _dirtyFlags |= EntityItem::DIRTY_UPDATEABLE; + _animationLoop.setFPS(value); +} + +QString ParticleEffectEntityItem::getAnimationSettings() const { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + QString value = _animationSettings; + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + + QVariant fpsValue(getAnimationFPS()); + settingsMap["fps"] = fpsValue; + + QVariant frameIndexValue(getAnimationFrameIndex()); + settingsMap["frameIndex"] = frameIndexValue; + + QVariant runningValue(getAnimationIsPlaying()); + settingsMap["running"] = runningValue; + + QVariant firstFrameValue(getAnimationFirstFrame()); + settingsMap["firstFrame"] = firstFrameValue; + + QVariant lastFrameValue(getAnimationLastFrame()); + settingsMap["lastFrame"] = lastFrameValue; + + QVariant loopValue(getAnimationLoop()); + settingsMap["loop"] = loopValue; + + QVariant holdValue(getAnimationHold()); + settingsMap["hold"] = holdValue; + + QVariant startAutomaticallyValue(getAnimationStartAutomatically()); + settingsMap["startAutomatically"] = startAutomaticallyValue; + + settingsAsJsonObject = QJsonObject::fromVariantMap(settingsMap); + QJsonDocument newDocument(settingsAsJsonObject); + QByteArray jsonByteArray = newDocument.toJson(QJsonDocument::Compact); + QString jsonByteString(jsonByteArray); + return jsonByteString; +} + +void ParticleEffectEntityItem::stepSim(float deltaTime) +{ + pa_xmin = pa_ymin = pa_zmin = -1.0; + pa_xmax = pa_ymax = pa_zmax = 1.0; + + // update particles + quint32 updateIter = pa_head; + while (pa_life[updateIter] > 0.0f) + { + pa_life[updateIter] -= deltaTime; + if (pa_life[updateIter] <= 0.0f) + { + pa_life[updateIter] = -1.0f; + pa_head = (pa_head + 1) % _maxParticles; + pa_count--; + } + else + { + // DUMB FORWARD EULER just to get it done + int j = updateIter * 3; + pa_position[j] += pa_velocity[j] * deltaTime; + pa_position[j+1] += pa_velocity[j+1] * deltaTime; + pa_position[j+2] += pa_velocity[j+2] * deltaTime; + + pa_xmin = glm::min(pa_xmin, pa_position[j]); + pa_ymin = glm::min(pa_ymin, pa_position[j+1]); + pa_zmin = glm::min(pa_zmin, pa_position[j+2]); + pa_xmax = glm::max(pa_xmax, pa_position[j]); + pa_ymax = glm::max(pa_ymax, pa_position[j + 1]); + pa_zmax = glm::max(pa_zmax, pa_position[j + 2]); + + // massless particles + pa_velocity[j + 1] += deltaTime * _localGravity; + } + updateIter = (updateIter + 1) % _maxParticles; + } + + // emit new particles + quint32 pa_emit_idx = updateIter; + partial_emit += ((float)_emitRate) * deltaTime; + quint32 birthed = (quint32)partial_emit; + partial_emit -= (float)birthed; + glm::vec3 randOffset; + + for (quint32 i = 0; i < birthed; i++) + { + if (pa_life[pa_emit_idx] < 0.0f) + { + int j = pa_emit_idx * 3; + pa_life[pa_emit_idx] = _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; + pa_velocity[j] = (_emitDirection.x * _emitStrength) + randOffset.x; + pa_velocity[j + 1] = (_emitDirection.y * _emitStrength) + randOffset.y; + pa_velocity[j + 2] = (_emitDirection.z * _emitStrength) + randOffset.z; + + // DUMB FORWARD EULER just to get it done + pa_position[j] += pa_velocity[j] * deltaTime; + pa_position[j + 1] += pa_velocity[j + 1] * deltaTime; + pa_position[j + 2] += pa_velocity[j + 2] * deltaTime; + + pa_xmin = glm::min(pa_xmin, pa_position[j]); + pa_ymin = glm::min(pa_ymin, pa_position[j + 1]); + pa_zmin = glm::min(pa_zmin, pa_position[j + 2]); + pa_xmax = glm::max(pa_xmax, pa_position[j]); + pa_ymax = glm::max(pa_ymax, pa_position[j + 1]); + pa_zmax = glm::max(pa_zmax, pa_position[j + 2]); + + // massless particles + pa_velocity[j + 1] += deltaTime * _localGravity; + + pa_emit_idx = (pa_emit_idx + 1) % _maxParticles; + pa_count++; + } + else + break; + } +} + +void ParticleEffectEntityItem::resetSim() +{ + for (int i = 0; i < _maxParticles; i++) + { + int j = i * 3; + pa_life[i] = -1.0f; + pa_position[j] = 0.0f; + pa_position[j+1] = 0.0f; + pa_position[j+2] = 0.0f; + pa_velocity[j] = 0.0f; + pa_velocity[j+1] = 0.0f; + pa_velocity[j+2] = 0.0f; + } + pa_count = 0; + pa_head = 0; + partial_emit = 0.0f; + + srand((unsigned int) this); +} + diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h new file mode 100644 index 0000000000..1306620bc6 --- /dev/null +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -0,0 +1,181 @@ +// +// 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. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ParticleEffectEntityItem_h +#define hifi_ParticleEffectEntityItem_h + +#include +#include "EntityItem.h" + +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); + + 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; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData); + + virtual void update(const quint64& now); + virtual bool needsToCallUpdate() const; + + const rgbColor& getColor() const { return _color; } + xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } + + void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } + void setColor(const xColor& value) { + _color[RED_INDEX] = value.red; + _color[GREEN_INDEX] = value.green; + _color[BLUE_INDEX] = value.blue; + } + + void updateShapeType(ShapeType type); + virtual ShapeType getShapeType() const { return _shapeType; } + + virtual void debugDump() const; + + static const float DEFAULT_ANIMATION_FRAME_INDEX; + void setAnimationFrameIndex(float value); + void setAnimationSettings(const QString& value); + + static const bool DEFAULT_ANIMATION_IS_PLAYING; + void setAnimationIsPlaying(bool value); + + static const float DEFAULT_ANIMATION_FPS; + void setAnimationFPS(float value); + + void setAnimationLoop(bool loop) { _animationLoop.setLoop(loop); } + bool getAnimationLoop() const { return _animationLoop.getLoop(); } + + void setAnimationHold(bool hold) { _animationLoop.setHold(hold); } + bool getAnimationHold() const { return _animationLoop.getHold(); } + + void setAnimationStartAutomatically(bool startAutomatically) { _animationLoop.setStartAutomatically(startAutomatically); } + bool getAnimationStartAutomatically() const { return _animationLoop.getStartAutomatically(); } + + void setAnimationFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); } + float getAnimationFirstFrame() const { return _animationLoop.getFirstFrame(); } + + void setAnimationLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } + float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } + + static const quint32 DEFAULT_MAX_PARTICLES; + void setMaxParticles(quint32 maxParticles) { _maxParticles = maxParticles; } + quint32 getMaxParticles() const { return _maxParticles; } + + static const float DEFAULT_LIFESPAN; + void setLifespan(float lifespan) { _lifespan = lifespan; } + float getLifespan() const { return _lifespan; } + + static const float DEFAULT_EMIT_RATE; + void setEmitRate(float emitRate) { _emitRate = emitRate; } + float getEmitRate() const { return _emitRate; } + + static const glm::vec3 DEFAULT_EMIT_DIRECTION; + void setEmitDirection(glm::vec3 emitDirection) { _emitDirection = emitDirection; } + const glm::vec3& getEmitDirection() const { return _emitDirection; } + + static const float DEFAULT_EMIT_STRENGTH; + void setEmitStrength(float emitStrength) { _emitStrength = emitStrength; } + float getEmitStrength() const { return _emitStrength; } + + static const float DEFAULT_LOCAL_GRAVITY; + void setLocalGravity(float localGravity) { _localGravity = localGravity; } + float getLocalGravity() const { return _localGravity; } + + static const float DEFAULT_PARTICLE_RADIUS; + void setParticleRadius(float particleRadius) { _particleRadius = particleRadius; } + float getParticleRadius() const { return _particleRadius; } + + bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); } + float getAnimationFrameIndex() const { return _animationLoop.getFrameIndex(); } + float getAnimationFPS() const { return _animationLoop.getFPS(); } + QString getAnimationSettings() const; + +protected: + + bool isAnimatingSomething() const; + void stepSim(float deltaTime); + void resetSim(); + + // the properties of this entity + rgbColor _color; + quint32 _maxParticles; + float _lifespan; + float _emitRate; + glm::vec3 _emitDirection; + float _emitStrength; + float _localGravity; + float _particleRadius; + quint64 _lastAnimated; + AnimationLoop _animationLoop; + QString _animationSettings; + ShapeType _shapeType = SHAPE_TYPE_NONE; + + // all the internals of running the particle sim + float* pa_life; + float* pa_position; + float* pa_velocity; + float partial_emit; + quint32 pa_count; + quint32 pa_head; + float pa_xmin; + float pa_xmax; + float pa_ymin; + float pa_ymax; + float pa_zmin; + float pa_zmax; + +}; + +#endif // hifi_ParticleEffectEntityItem_h +