diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 309671f49e..89c2f0dccf 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -144,6 +144,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn particleUniforms.color.middle = _particleProperties.getColorMiddle(); particleUniforms.color.finish = _particleProperties.getColorFinish(); particleUniforms.color.spread = _particleProperties.getColorSpread(); + particleUniforms.spin.start = _particleProperties.spin.range.start; + particleUniforms.spin.middle = _particleProperties.spin.gradient.target; + particleUniforms.spin.finish = _particleProperties.spin.range.finish; + particleUniforms.spin.spread = _particleProperties.spin.gradient.spread; particleUniforms.lifespan = _particleProperties.lifespan; }); // Update particle uniforms diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index 8e9353894a..e1e3cd210e 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -74,6 +74,7 @@ private: struct ParticleUniforms { InterpolationData radius; InterpolationData color; // rgba + InterpolationData spin; float lifespan; glm::vec3 spare; }; diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv index 7653bc0a42..bc3ef4fbb0 100644 --- a/libraries/entities-renderer/src/textured_particle.slv +++ b/libraries/entities-renderer/src/textured_particle.slv @@ -27,10 +27,17 @@ struct Colors { vec4 finish; vec4 spread; }; +struct Spin { + float start; + float middle; + float finish; + float spread; +}; struct ParticleUniforms { Radii radius; Colors color; + Spin spin; vec4 lifespan; // x is lifespan, 3 spare floats }; @@ -44,15 +51,6 @@ layout(location=2) in vec2 inColor; // This is actual Lifetime + Seed out vec4 varColor; out vec2 varTexcoord; -const int NUM_VERTICES_PER_PARTICLE = 4; -// This ordering ensures that un-rotated particles render upright in the viewer. -const vec4 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec4[NUM_VERTICES_PER_PARTICLE]( - vec4(-1.0, 1.0, 0.0, 0.0), - vec4(-1.0, -1.0, 0.0, 0.0), - vec4(1.0, 1.0, 0.0, 0.0), - vec4(1.0, -1.0, 0.0, 0.0) -); - float bezierInterpolate(float y1, float y2, float y3, float u) { // https://en.wikipedia.org/wiki/Bezier_curve return (1.0 - u) * (1.0 - u) * y1 + 2.0 * (1.0 - u) * u * y2 + u * u * y3; @@ -103,6 +101,15 @@ vec4 interpolate3Vec4(vec4 y1, vec4 y2, vec4 y3, float u) { interpolate3Points(y1.w, y2.w, y3.w, u)); } +const int NUM_VERTICES_PER_PARTICLE = 4; +const vec2 TEX_COORDS[NUM_VERTICES_PER_PARTICLE] = vec2[NUM_VERTICES_PER_PARTICLE]( + vec2(-1.0, 0.0), + vec2(-1.0, 1.0), + vec2(0.0, 0.0), + vec2(0.0, 1.0) +); + + void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); @@ -116,25 +123,47 @@ void main(void) { float age = inColor.x / particle.lifespan.x; float seed = inColor.y; - // Pass the texcoord and the z texcoord is representing the texture icon - // Offset for corrected vertex ordering. - varTexcoord = vec2((UNIT_QUAD[twoTriID].xy -1.0) * vec2(0.5, -0.5)); + // Pass the texcoord + varTexcoord = TEX_COORDS[twoTriID].xy; varColor = interpolate3Vec4(particle.color.start, particle.color.middle, particle.color.finish, age); vec3 colorSpread = 2.0 * vec3(hifi_hash(seed), hifi_hash(seed * 2.0), hifi_hash(seed * 3.0)) - 1.0; varColor.rgb = clamp(varColor.rgb + colorSpread * particle.color.spread.rgb, vec3(0), vec3(1)); float alphaSpread = 2.0 * hifi_hash(seed * 4.0) - 1.0; varColor.a = clamp(varColor.a + alphaSpread * particle.color.spread.a, 0.0, 1.0); + float spin = interpolate3Points(particle.spin.start, particle.spin.middle, particle.spin.finish, age); + float spinSpread = 2.0 * hifi_hash(seed * 5.0) - 1.0; + spin = spin + spinSpread * particle.spin.spread; + // anchor point in eye space float radius = interpolate3Points(particle.radius.start, particle.radius.middle, particle.radius.finish, age); - float radiusSpread = 2.0 * hifi_hash(seed * 5.0) - 1.0; + float radiusSpread = 2.0 * hifi_hash(seed * 6.0) - 1.0; radius = max(radius + radiusSpread * particle.radius.spread, 0.0); - vec4 quadPos = radius * UNIT_QUAD[twoTriID]; vec4 anchorPoint; vec4 _inPosition = vec4(inPosition, 1.0); <$transformModelToEyePos(cam, obj, _inPosition, anchorPoint)$> - vec4 eyePos = anchorPoint + quadPos; + mat3 view3 = mat3(cam._view); + vec3 worldUpEye = normalize(view3 * vec3(0, 1, 0)); + vec3 right = cross(vec3(0, 0, -1), worldUpEye); + vec3 up = cross(right, vec3(0, 0, -1)); + // This ordering ensures that un-rotated particles render upright in the viewer. + vec3 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec3[NUM_VERTICES_PER_PARTICLE]( + normalize(-right + up), + normalize(-right - up), + normalize(right + up), + normalize(right - up) + ); + float c = cos(spin); + float s = sin(spin); + mat4 rotation = mat4( + c, -s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); + vec4 quadPos = radius * vec4(UNIT_QUAD[twoTriID], 0.0); + vec4 eyePos = anchorPoint + rotation * quadPos; <$transformEyeToClipPos(cam, eyePos, gl_Position)$> } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index efd2376677..929adb2c37 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -369,6 +369,10 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_DATA, materialData); CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); + CHECK_PROPERTY_CHANGE(PROP_PARTICLE_SPIN, particleSpin); + CHECK_PROPERTY_CHANGE(PROP_SPIN_SPREAD, spinSpread); + CHECK_PROPERTY_CHANGE(PROP_SPIN_START, spinStart); + CHECK_PROPERTY_CHANGE(PROP_SPIN_FINISH, spinFinish); // Certifiable Properties CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); @@ -908,6 +912,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * alpha value is used. * @property {number} alphaSpread=0 - The spread in alpha that each particle is given. If alpha == 0.5 * and alphaSpread == 0.25, each particle will have an alpha in the range 0.250.75. + * @property {number} particleSpin=0 - The spin of each particle at the middle of its life. -2*PI - 2*PI. + * @property {number} spinStart=null - The spin of each particle at the start of its life. In the range -2*PI2*PI. + * If null, the particleSpin value is used. + * @property {number} spinFinish=null - The spin of each particle at the end of its life. In the range -2*PI2*PI. + * If null, the particleSpin value is used. + * @property {number} spinSpread=0 - The spread in spin that each particle is given. -2*PI - 2*PI. If particleSpin == PI + * and spinSpread == PI/2, each particle will have a spin in the range PI/23*PI/2. * * @property {ShapeType} shapeType="none" - Currently not used. Read-only. * @@ -1291,6 +1302,10 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMITTER_SHOULD_TRAIL, emitterShouldTrail); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_SPIN, particleSpin); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_SPREAD, spinSpread); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_START, spinStart); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_FINISH, spinFinish); } // Models only @@ -1583,6 +1598,10 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialData, QString, setMaterialData); COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera); + COPY_PROPERTY_FROM_QSCRIPTVALUE(particleSpin, float, setParticleSpin); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinSpread, float, setSpinSpread); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinStart, float, setSpinStart); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinFinish, float, setSpinFinish); // Certifiable Properties COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); @@ -1751,6 +1770,10 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(radiusSpread); COPY_PROPERTY_IF_CHANGED(radiusStart); COPY_PROPERTY_IF_CHANGED(radiusFinish); + COPY_PROPERTY_IF_CHANGED(particleSpin); + COPY_PROPERTY_IF_CHANGED(spinSpread); + COPY_PROPERTY_IF_CHANGED(spinStart); + COPY_PROPERTY_IF_CHANGED(spinFinish); // Certifiable Properties COPY_PROPERTY_IF_CHANGED(itemName); @@ -1964,6 +1987,11 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool); + ADD_PROPERTY_TO_MAP(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_START, SpinStart, spinStart, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_FINISH, SpinFinish, spinFinish, float); + // Certifiable Properties ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString); ADD_PROPERTY_TO_MAP(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString); @@ -2292,6 +2320,10 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_ALPHA_START, properties.getAlphaStart()); APPEND_ENTITY_PROPERTY(PROP_ALPHA_FINISH, properties.getAlphaFinish()); APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, properties.getEmitterShouldTrail()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, properties.getParticleSpin()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, properties.getSpinSpread()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_START, properties.getSpinStart()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_FINISH, properties.getSpinFinish()); } if (properties.getType() == EntityTypes::Zone) { @@ -2667,6 +2699,10 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_START, float, setAlphaStart); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_FINISH, float, setAlphaFinish); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_SPIN, float, setParticleSpin); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_SPREAD, float, setSpinSpread); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_START, float, setSpinStart); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_FINISH, float, setSpinFinish); } if (properties.getType() == EntityTypes::Zone) { @@ -2961,6 +2997,10 @@ void EntityItemProperties::markAllChanged() { _colorFinishChanged = true; _alphaStartChanged = true; _alphaFinishChanged = true; + _particleSpinChanged = true; + _spinStartChanged = true; + _spinFinishChanged = true; + _spinSpreadChanged = true; _materialURLChanged = true; _materialMappingModeChanged = true; @@ -3309,6 +3349,18 @@ QList EntityItemProperties::listChangedProperties() { if (radiusFinishChanged()) { out += "radiusFinish"; } + if (particleSpinChanged()) { + out += "particleSpin"; + } + if (spinSpreadChanged()) { + out += "spinSpread"; + } + if (spinStartChanged()) { + out += "spinStart"; + } + if (spinFinishChanged()) { + out += "spinFinish"; + } if (materialURLChanged()) { out += "materialURL"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 93b8c991d5..7799d45b22 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -235,6 +235,11 @@ public: DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA); + DEFINE_PROPERTY(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float, particle::DEFAULT_PARTICLE_SPIN); + DEFINE_PROPERTY(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float, particle::DEFAULT_SPIN_SPREAD); + DEFINE_PROPERTY(PROP_SPIN_START, SpinStart, spinStart, float, particle::DEFAULT_SPIN_START); + DEFINE_PROPERTY(PROP_SPIN_FINISH, SpinFinish, spinFinish, float, particle::DEFAULT_SPIN_FINISH) + // Certifiable Properties - related to Proof of Purchase certificates DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME); DEFINE_PROPERTY_REF(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString, ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index d43a991f22..bece248fe7 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -251,6 +251,11 @@ enum EntityPropertyList { PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire, only used locally + PROP_PARTICLE_SPIN, + PROP_SPIN_START, + PROP_SPIN_FINISH, + PROP_SPIN_SPREAD, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 18cf7c9252..c1785639e0 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -91,6 +91,7 @@ bool operator==(const Properties& a, const Properties& b) { (a.color == b.color) && (a.alpha == b.alpha) && (a.radius == b.radius) && + (a.spin == b.spin) && (a.radiusStart == b.radiusStart) && (a.lifespan == b.lifespan) && (a.maxParticles == b.maxParticles) && @@ -130,7 +131,11 @@ bool Properties::valid() const { (radius.gradient.target == glm::clamp(radius.gradient.target, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.start == glm::clamp(radius.range.start, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.finish == glm::clamp(radius.range.finish, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && - (radius.gradient.spread == glm::clamp(radius.gradient.spread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)); + (radius.gradient.spread == glm::clamp(radius.gradient.spread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && + (spin.gradient.target == glm::clamp(spin.gradient.target, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.range.start == glm::clamp(spin.range.start, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.range.finish == glm::clamp(spin.range.finish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)); } bool Properties::emitting() const { @@ -332,6 +337,43 @@ void ParticleEffectEntityItem::setRadiusSpread(float radiusSpread) { } } +void ParticleEffectEntityItem::setParticleSpin(float particleSpin) { + particleSpin = glm::clamp(particleSpin, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (particleSpin != _particleProperties.spin.gradient.target) { + withWriteLock([&] { + _particleProperties.spin.gradient.target = particleSpin; + }); + } +} + +void ParticleEffectEntityItem::setSpinStart(float spinStart) { + spinStart = + glm::isnan(spinStart) ? spinStart : glm::clamp(spinStart, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinStart != _particleProperties.spin.range.start) { + withWriteLock([&] { + _particleProperties.spin.range.start = spinStart; + }); + } +} + +void ParticleEffectEntityItem::setSpinFinish(float spinFinish) { + spinFinish = + glm::isnan(spinFinish) ? spinFinish : glm::clamp(spinFinish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinFinish != _particleProperties.spin.range.finish) { + withWriteLock([&] { + _particleProperties.spin.range.finish = spinFinish; + }); + } +} + +void ParticleEffectEntityItem::setSpinSpread(float spinSpread) { + spinSpread = glm::clamp(spinSpread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinSpread != _particleProperties.spin.gradient.spread) { + withWriteLock([&] { + _particleProperties.spin.gradient.spread = spinSpread; + }); + } +} void ParticleEffectEntityItem::computeAndUpdateDimensions() { particle::Properties particleProperties; @@ -398,6 +440,10 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(EntityPropertyFlags COPY_ENTITY_PROPERTY_TO_PROPERTIES(alphaFinish, getAlphaFinish); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitterShouldTrail, getEmitterShouldTrail); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleSpin, getParticleSpin); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinSpread, getSpinSpread); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinStart, getSpinStart); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinFinish, getSpinFinish); return properties; } @@ -435,6 +481,10 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(alphaFinish, setAlphaFinish); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitterShouldTrail, setEmitterShouldTrail); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleSpin, setParticleSpin); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinSpread, setSpinSpread); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinStart, setSpinStart); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinFinish, setSpinFinish); if (somethingChanged) { bool wantDebug = false; @@ -515,6 +565,11 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail); + READ_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, float, setParticleSpin); + READ_ENTITY_PROPERTY(PROP_SPIN_SPREAD, float, setSpinSpread); + READ_ENTITY_PROPERTY(PROP_SPIN_START, float, setSpinStart); + READ_ENTITY_PROPERTY(PROP_SPIN_FINISH, float, setSpinFinish); + return bytesRead; } @@ -551,6 +606,10 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea requestedProperties += PROP_AZIMUTH_START; requestedProperties += PROP_AZIMUTH_FINISH; requestedProperties += PROP_EMITTER_SHOULD_TRAIL; + requestedProperties += PROP_PARTICLE_SPIN; + requestedProperties += PROP_SPIN_SPREAD; + requestedProperties += PROP_SPIN_START; + requestedProperties += PROP_SPIN_FINISH; return requestedProperties; } @@ -594,6 +653,10 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_START, getAzimuthStart()); APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, getAzimuthFinish()); APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, getEmitterShouldTrail()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, getParticleSpin()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, getSpinSpread()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_START, getSpinStart()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_FINISH, getSpinFinish()); } @@ -689,6 +752,12 @@ particle::Properties ParticleEffectEntityItem::getParticleProperties() const { if (glm::isnan(result.radius.range.finish)) { result.radius.range.finish = getParticleRadius(); } + if (glm::isnan(result.spin.range.start)) { + result.spin.range.start = getParticleSpin(); + } + if (glm::isnan(result.spin.range.finish)) { + result.spin.range.finish = getParticleSpin(); + } }); if (!result.valid()) { diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 7e507ab46a..e4120eed63 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -39,7 +39,7 @@ namespace particle { static const float MINIMUM_EMIT_RATE = 0.0f; static const float MAXIMUM_EMIT_RATE = 100000.0f; static const float DEFAULT_EMIT_SPEED = 5.0f; - static const float MINIMUM_EMIT_SPEED = 0.0f; + static const float MINIMUM_EMIT_SPEED = -1000.0f; static const float MAXIMUM_EMIT_SPEED = 1000.0f; // Approx mach 3 static const float DEFAULT_SPEED_SPREAD = 1.0f; static const glm::quat DEFAULT_EMIT_ORIENTATION = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_X); // Vertical @@ -69,6 +69,12 @@ namespace particle { static const float DEFAULT_RADIUS_SPREAD = 0.0f; static const float DEFAULT_RADIUS_START = UNINITIALIZED; static const float DEFAULT_RADIUS_FINISH = UNINITIALIZED; + static const float DEFAULT_PARTICLE_SPIN = 0.0f; + static const float DEFAULT_SPIN_START = UNINITIALIZED; + static const float DEFAULT_SPIN_FINISH = UNINITIALIZED; + static const float DEFAULT_SPIN_SPREAD = 0.0f; + static const float MINIMUM_PARTICLE_SPIN = -2.0f * SCRIPT_MAXIMUM_PI; + static const float MAXIMUM_PARTICLE_SPIN = 2.0f * SCRIPT_MAXIMUM_PI; static const QString DEFAULT_TEXTURES = ""; static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false; @@ -151,6 +157,7 @@ namespace particle { RangeGradient alpha { DEFAULT_ALPHA, DEFAULT_ALPHA_START, DEFAULT_ALPHA_FINISH, DEFAULT_ALPHA_SPREAD }; float radiusStart { DEFAULT_EMIT_RADIUS_START }; RangeGradient radius { DEFAULT_PARTICLE_RADIUS, DEFAULT_RADIUS_START, DEFAULT_RADIUS_FINISH, DEFAULT_RADIUS_SPREAD }; + RangeGradient spin { DEFAULT_PARTICLE_SPIN, DEFAULT_SPIN_START, DEFAULT_SPIN_FINISH, DEFAULT_SPIN_SPREAD }; float lifespan { DEFAULT_LIFESPAN }; uint32_t maxParticles { DEFAULT_MAX_PARTICLES }; EmitProperties emission; @@ -168,6 +175,7 @@ namespace particle { Properties& operator =(const Properties& other) { color = other.color; alpha = other.alpha; + spin = other.spin; radius = other.radius; lifespan = other.lifespan; maxParticles = other.maxParticles; @@ -306,6 +314,18 @@ public: void setRadiusSpread(float radiusSpread); float getRadiusSpread() const { return _particleProperties.radius.gradient.spread; } + void setParticleSpin(float particleSpin); + float getParticleSpin() const { return _particleProperties.spin.gradient.target; } + + void setSpinStart(float spinStart); + float getSpinStart() const { return _particleProperties.spin.range.start; } + + void setSpinFinish(float spinFinish); + float getSpinFinish() const { return _particleProperties.spin.range.finish; } + + void setSpinSpread(float spinSpread); + float getSpinSpread() const { return _particleProperties.spin.gradient.spread; } + void computeAndUpdateDimensions(); void setTextures(const QString& textures); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 13ffcb5120..d0bd7fc872 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -33,7 +33,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::ParticleEntityFix); + return static_cast(EntityVersion::ParticleSpin); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 6e1aca83e5..2ffadfef4b 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -238,7 +238,8 @@ enum class EntityVersion : PacketVersion { CloneableData, CollisionMask16Bytes, YieldSimulationOwnership, - ParticleEntityFix + ParticleEntityFix, + ParticleSpin }; enum class EntityScriptCallMethodVersion : PacketVersion { diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index cb2c2f3374..b3a4461e69 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -361,6 +361,48 @@ type: "Row" } ], + Spim: [ + { + id: "particleSpin", + name: "Particle Spin", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinSpread", + name: "Spin Spread", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinStart", + name: "Spin Start", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinFinish", + name: "Spin Finish", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + } + ], Polar: [ { id: "polarStart", diff --git a/scripts/system/particle_explorer/particleExplorerTool.js b/scripts/system/particle_explorer/particleExplorerTool.js index 1914180ff9..a3be004329 100644 --- a/scripts/system/particle_explorer/particleExplorerTool.js +++ b/scripts/system/particle_explorer/particleExplorerTool.js @@ -75,6 +75,12 @@ ParticleExplorerTool = function(createToolsWindow) { if (isNaN(properties.colorFinish.red)) { properties.colorFinish = properties.color; } + if (isNaN(properties.spinStart)) { + properties.spinStart = properties.particleSpin; + } + if (isNaN(properties.spinFinish)) { + properties.spinFinish = properties.particleSpin; + } sendParticleProperties(properties); } @@ -88,8 +94,8 @@ ParticleExplorerTool = function(createToolsWindow) { if (data.messageType === "settings_update") { var updatedSettings = data.updatedSettings; - var optionalProps = ["alphaStart", "alphaFinish", "radiusStart", "radiusFinish", "colorStart", "colorFinish"]; - var fallbackProps = ["alpha", "particleRadius", "color"]; + var optionalProps = ["alphaStart", "alphaFinish", "radiusStart", "radiusFinish", "colorStart", "colorFinish", "spinStart", "spinFinish"]; + var fallbackProps = ["alpha", "particleRadius", "color", "particleSpin"]; for (var i = 0; i < optionalProps.length; i++) { var fallbackProp = fallbackProps[Math.floor(i / 2)]; var optionalValue = updatedSettings[optionalProps[i]];