From ea501331464bf10ebfeaf85f75e7cad263cdb8c1 Mon Sep 17 00:00:00 2001
From: SamGondelman <samuel@highfidelity.io>
Date: Mon, 18 Mar 2019 12:05:17 -0700
Subject: [PATCH 1/6] working on adding particle shape types

---
 .../src/RenderableModelEntityItem.cpp         |   4 -
 .../RenderableParticleEffectEntityItem.cpp    | 117 ++++++++++++------
 .../src/RenderableParticleEffectEntityItem.h  |  10 +-
 .../entities/src/EntityItemProperties.cpp     |  26 ++--
 .../entities/src/ParticleEffectEntityItem.cpp |  23 ++++
 .../entities/src/ParticleEffectEntityItem.h   |   9 +-
 libraries/entities/src/ZoneEntityItem.cpp     |   4 -
 7 files changed, 138 insertions(+), 55 deletions(-)

diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index 643e5afb70..2fdffde8a3 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -307,10 +307,6 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) {
 }
 
 void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
-    // because the caching system only allows one Geometry per url, and because this url might also be used
-    // as a visual model, we need to change this url in some way.  We add a "collision-hull" query-arg so it
-    // will end up in a different hash-key in ResourceCache.  TODO: It would be better to use the same URL and
-    // parse it twice.
     auto currentCompoundShapeURL = getCompoundShapeURL();
     ModelEntityItem::setCompoundShapeURL(url);
     if (getCompoundShapeURL() != currentCompoundShapeURL || !getModel()) {
diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
index c139fbf320..2168347554 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
@@ -9,13 +9,11 @@
 //
 
 #include "RenderableParticleEffectEntityItem.h"
-
 #include <StencilMaskPass.h>
 
 #include <GeometryCache.h>
 #include <shaders/Shaders.h>
 
-
 using namespace render;
 using namespace render::entities;
 
@@ -79,6 +77,14 @@ bool ParticleEffectEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedE
         return true;
     }
 
+    if (_shapeType != entity->getShapeType()) {
+        return true;
+    }
+
+    if (_compoundShapeURL != entity->getCompoundShapeURL()) {
+        return true;
+    }
+
     return false;
 }
 
@@ -87,11 +93,17 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
     if (!newParticleProperties.valid()) {
         qCWarning(entitiesrenderer) << "Bad particle properties";
     }
-    
-    if (resultWithReadLock<bool>([&]{ return _particleProperties != newParticleProperties; })) {
+
+    if (resultWithReadLock<bool>([&] { return _particleProperties != newParticleProperties; })) {
         _timeUntilNextEmit = 0;
-        withWriteLock([&]{
+        withWriteLock([&] {
             _particleProperties = newParticleProperties;
+            _shapeType = entity->getShapeType();
+            QString compoundShapeURL = entity->getCompoundShapeURL();
+            if (_compoundShapeURL != compoundShapeURL) {
+                _compoundShapeURL = compoundShapeURL;
+                fetchGeometryResource();
+            }
             if (!_prevEmitterShouldTrailInitialized) {
                 _prevEmitterShouldTrailInitialized = true;
                 _prevEmitterShouldTrail = _particleProperties.emission.shouldTrail;
@@ -104,10 +116,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
     });
     _emitting = entity->getIsEmitting();
 
-    bool textureEmpty = resultWithReadLock<bool>([&]{ return _particleProperties.textures.isEmpty(); });
+    bool textureEmpty = resultWithReadLock<bool>([&] { return _particleProperties.textures.isEmpty(); });
     if (textureEmpty) {
         if (_networkTexture) {
-            withWriteLock([&] { 
+            withWriteLock([&] {
                 _networkTexture.reset();
             });
         }
@@ -116,11 +128,11 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
             entity->setVisuallyReady(true);
         });
     } else {
-        bool textureNeedsUpdate = resultWithReadLock<bool>([&]{
+        bool textureNeedsUpdate = resultWithReadLock<bool>([&] {
             return !_networkTexture || _networkTexture->getURL() != QUrl(_particleProperties.textures);
         });
         if (textureNeedsUpdate) {
-            withWriteLock([&] { 
+            withWriteLock([&] {
                 _networkTexture = DependencyManager::get<TextureCache>()->getTexture(_particleProperties.textures);
             });
         }
@@ -144,7 +156,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
 void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
     // Fill in Uniforms structure
     ParticleUniforms particleUniforms;
-    withReadLock([&]{
+    withReadLock([&] {
         particleUniforms.radius.start = _particleProperties.radius.range.start;
         particleUniforms.radius.middle = _particleProperties.radius.gradient.target;
         particleUniforms.radius.finish = _particleProperties.radius.range.finish;
@@ -183,7 +195,8 @@ Item::Bound ParticleEffectEntityRenderer::getBound() {
 
 static const size_t VERTEX_PER_PARTICLE = 4;
 
-ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties) {
+ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties,
+                                                                                       const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource) {
     CpuParticle particle;
 
     const auto& accelerationSpread = particleProperties.emission.acceleration.spread;
@@ -221,33 +234,53 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
 
         float azimuth;
         if (azimuthFinish >= azimuthStart) {
-            azimuth = azimuthStart + (azimuthFinish - azimuthStart) *  randFloat();
+            azimuth = azimuthStart + (azimuthFinish - azimuthStart) * randFloat();
         } else {
             azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat();
         }
 
-        if (emitDimensions == Vectors::ZERO) {
+        if (emitDimensions == Vectors::ZERO || shapeType == ShapeType::SHAPE_TYPE_NONE) {
             // Point
             emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z;
         } else {
-            // Ellipsoid
-            float radiusScale = 1.0f;
-            if (emitRadiusStart < 1.0f) {
-                float randRadius =
-                    emitRadiusStart + randFloatInRange(0.0f, particle::MAXIMUM_EMIT_RADIUS_START - emitRadiusStart);
-                radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f);
+            glm::vec3 emitPosition;
+            switch (shapeType) {
+                case ShapeType::SHAPE_TYPE_BOX:
+
+                case ShapeType::SHAPE_TYPE_CAPSULE_X:
+                case ShapeType::SHAPE_TYPE_CAPSULE_Y:
+                case ShapeType::SHAPE_TYPE_CAPSULE_Z:
+
+                case ShapeType::SHAPE_TYPE_CYLINDER_X:
+                case ShapeType::SHAPE_TYPE_CYLINDER_Y:
+                case ShapeType::SHAPE_TYPE_CYLINDER_Z:
+
+                case ShapeType::SHAPE_TYPE_CIRCLE:
+                case ShapeType::SHAPE_TYPE_PLANE:
+
+                case ShapeType::SHAPE_TYPE_COMPOUND:
+
+                case ShapeType::SHAPE_TYPE_SPHERE:
+                case ShapeType::SHAPE_TYPE_ELLIPSOID:
+                default: {
+                    float radiusScale = 1.0f;
+                    if (emitRadiusStart < 1.0f) {
+                        float randRadius =
+                            emitRadiusStart + randFloatInRange(0.0f, particle::MAXIMUM_EMIT_RADIUS_START - emitRadiusStart);
+                        radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f);
+                    }
+
+                    glm::vec3 radii = radiusScale * 0.5f * emitDimensions;
+                    float x = radii.x * glm::cos(elevation) * glm::cos(azimuth);
+                    float y = radii.y * glm::cos(elevation) * glm::sin(azimuth);
+                    float z = radii.z * glm::sin(elevation);
+                    emitPosition = glm::vec3(x, y, z);
+                    emitDirection = glm::normalize(glm::vec3(radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f,
+                                                             radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f,
+                                                             radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f));
+                }
             }
 
-            glm::vec3 radii = radiusScale * 0.5f * emitDimensions;
-            float x = radii.x * glm::cos(elevation) * glm::cos(azimuth);
-            float y = radii.y * glm::cos(elevation) * glm::sin(azimuth);
-            float z = radii.z * glm::sin(elevation);
-            glm::vec3 emitPosition = glm::vec3(x, y, z);
-            emitDirection = glm::normalize(glm::vec3(
-                radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f,
-                radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f,
-                radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f
-            ));
             particle.relativePosition += emitOrientation * emitPosition;
         }
     }
@@ -267,20 +300,25 @@ void ParticleEffectEntityRenderer::stepSimulation() {
     const auto now = usecTimestampNow();
     const auto interval = std::min<uint64_t>(USECS_PER_SECOND / 60, now - _lastSimulated);
     _lastSimulated = now;
-    
+
     particle::Properties particleProperties;
-    withReadLock([&]{
+    ShapeType shapeType;
+    GeometryResource::Pointer geometryResource;
+    withReadLock([&] {
         particleProperties = _particleProperties;
+        shapeType = _shapeType;
+        geometryResource = _geometryResource;
     });
 
     const auto& modelTransform = getModelTransform();
-    if (_emitting && particleProperties.emitting()) {
+    if (_emitting && particleProperties.emitting() &&
+        (_shapeType != ShapeType::SHAPE_TYPE_COMPOUND || (_geometryResource && _geometryResource->isLoaded()))) {
         uint64_t emitInterval = particleProperties.emitIntervalUsecs();
         if (emitInterval > 0 && interval >= _timeUntilNextEmit) {
             auto timeRemaining = interval;
             while (timeRemaining > _timeUntilNextEmit) {
                 // emit particle
-                _cpuParticles.push_back(createParticle(now, modelTransform, particleProperties));
+                _cpuParticles.push_back(createParticle(now, modelTransform, particleProperties, shapeType, geometryResource));
                 _timeUntilNextEmit = emitInterval;
                 if (emitInterval < timeRemaining) {
                     timeRemaining -= emitInterval;
@@ -297,7 +335,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
     }
 
     const float deltaTime = (float)interval / (float)USECS_PER_SECOND;
-    // update the particles 
+    // update the particles
     for (auto& particle : _cpuParticles) {
         if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) {
             if (_prevEmitterShouldTrail) {
@@ -313,7 +351,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
     static GpuParticles gpuParticles;
     gpuParticles.clear();
     gpuParticles.reserve(_cpuParticles.size()); // Reserve space
-    std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform](const CpuParticle& particle) {
+    std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform] (const CpuParticle& particle) {
         glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation());
         return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed));
     });
@@ -358,3 +396,12 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) {
     auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle);
     batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE);
 }
+
+void ParticleEffectEntityRenderer::fetchGeometryResource() {
+    QUrl hullURL(_compoundShapeURL);
+    if (hullURL.isEmpty()) {
+        _geometryResource.reset();
+    } else {
+        _geometryResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
+    }
+}
\ No newline at end of file
diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h
index 853d5cac29..4a4e5e5cbc 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h
@@ -81,7 +81,8 @@ private:
         glm::vec2 spare;
     };
 
-    static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties);
+    static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties,
+                                      const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource);
     void stepSimulation();
 
     particle::Properties _particleProperties;
@@ -90,11 +91,16 @@ private:
     CpuParticles _cpuParticles;
     bool _emitting { false };
     uint64_t _timeUntilNextEmit { 0 };
-    BufferPointer _particleBuffer{ std::make_shared<Buffer>() };
+    BufferPointer _particleBuffer { std::make_shared<Buffer>() };
     BufferView _uniformBuffer;
     quint64 _lastSimulated { 0 };
 
     PulsePropertyGroup _pulseProperties;
+    ShapeType _shapeType;
+    QString _compoundShapeURL;
+
+    void fetchGeometryResource();
+    GeometryResource::Pointer _geometryResource;
 
     NetworkTexturePointer _networkTexture;
     ScenePointer _scene;
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index 3efedf02ec..0a6875b63d 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -1114,23 +1114,28 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
  *     default, particles emit along the entity's local z-axis, and <code>azimuthStart</code> and <code>azimuthFinish</code> 
  *     are relative to the entity's local x-axis. The default value is a rotation of -90 degrees about the local x-axis, i.e., 
  *     the particles emit vertically.
- * @property {Vec3} emitDimensions=0,0,0 - The dimensions of the ellipsoid from which particles are emitted.
- * @property {number} emitRadiusStart=1 - The starting radius within the ellipsoid at which particles start being emitted;
- *     range <code>0.0</code> &ndash; <code>1.0</code> for the ellipsoid center to the ellipsoid surface, respectively.
- *     Particles are emitted from the portion of the ellipsoid that lies between <code>emitRadiusStart</code> and the 
- *     ellipsoid's surface.
+ * @property {Vec3} emitDimensions=0,0,0 - The dimensions of the shape from which particles are emitted.  The shape is specified with
+ *     <code>shapeType</code>.
+ * @property {number} emitRadiusStart=1 - The starting radius within the shape at which particles start being emitted;
+ *     range <code>0.0</code> &ndash; <code>1.0</code> for the center to the surface, respectively.
+ *     Particles are emitted from the portion of the shape that lies between <code>emitRadiusStart</code> and the 
+ *     shape's surface.
  * @property {number} polarStart=0 - The angle in radians from the entity's local z-axis at which particles start being emitted 
  *     within the ellipsoid; range <code>0</code> &ndash; <code>Math.PI</code>. Particles are emitted from the portion of the 
- *     ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.
+ *     ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.  Only used if <code>shapeType</code> is
+ *     <code>ellipsoid</code>.
  * @property {number} polarFinish=0 - The angle in radians from the entity's local z-axis at which particles stop being emitted 
  *     within the ellipsoid; range <code>0</code> &ndash; <code>Math.PI</code>. Particles are emitted from the portion of the 
- *     ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.
+ *     ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.  Only used if <code>shapeType</code> is
+ *     <code>ellipsoid</code>.
  * @property {number} azimuthStart=-Math.PI - The angle in radians from the entity's local x-axis about the entity's local 
  *     z-axis at which particles start being emitted; range <code>-Math.PI</code> &ndash; <code>Math.PI</code>. Particles are 
  *     emitted from the portion of the ellipsoid that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
+ *     Only used if <code>shapeType</code> is <code>ellipsoid</code>.
  * @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local
  *     z-axis at which particles stop being emitted; range <code>-Math.PI</code> &ndash; <code>Math.PI</code>. Particles are
  *     emitted from the portion of the ellipsoid that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
+ *     Only used if <code>shapeType</code> is <code>ellipsoid</code>.
  *
  * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency,
  *     use PNG format.
@@ -1170,7 +1175,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
  * up in the world.  If true, they will point towards the entity's up vector, based on its orientation.
  * @property {Entities.Pulse} pulse - The pulse-related properties.  Deprecated.
  *
- * @property {ShapeType} shapeType="none" - <em>Currently not used.</em> <em>Read-only.</em>
+ * @property {ShapeType} shapeType="ellipsoid" - The shape of the collision hull used if collisions are enabled.
+ * @property {string} compoundShapeURL="" - The model file to use for the compound shape if <code>shapeType</code> is
+ *     <code>"compound"</code>.
  *
  * @example <caption>Create a ball of green smoke.</caption>
  * particles = Entities.addEntity({
@@ -1658,6 +1665,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
     // Particles only
     if (_type == EntityTypes::ParticleEffect) {
         COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString());
+        COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL);
         COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color);
         COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
         _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
@@ -3104,6 +3112,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
 
             if (properties.getType() == EntityTypes::ParticleEffect) {
                 APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType()));
+                APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL());
                 APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
                 APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
                 _staticPulse.setProperties(properties);
@@ -3584,6 +3593,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
 
     if (properties.getType() == EntityTypes::ParticleEffect) {
         READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType);
+        READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
         READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor);
         READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
         properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp
index b916ecc3de..801cf56b32 100644
--- a/libraries/entities/src/ParticleEffectEntityItem.cpp
+++ b/libraries/entities/src/ParticleEffectEntityItem.cpp
@@ -410,6 +410,7 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropert
     EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class
 
     COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType);
+    COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL);
     COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor);
     COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
     withReadLock([&] {
@@ -464,6 +465,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
     bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
 
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
+    SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
     withWriteLock([&] {
@@ -540,6 +542,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
     const unsigned char* dataAt = data;
 
     READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
+    READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
     READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor);
     READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
     withWriteLock([&] {
@@ -598,6 +601,7 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea
     EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
 
     requestedProperties += PROP_SHAPE_TYPE;
+    requestedProperties += PROP_COMPOUND_SHAPE_URL;
     requestedProperties += PROP_COLOR;
     requestedProperties += PROP_ALPHA;
     requestedProperties += _pulseProperties.getEntityProperties(params);
@@ -656,6 +660,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData,
 
     bool successPropertyFits = true;
     APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType());
+    APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL());
     APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
     APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
     withReadLock([&] {
@@ -726,6 +731,24 @@ void ParticleEffectEntityItem::setShapeType(ShapeType type) {
     });
 }
 
+ShapeType ParticleEffectEntityItem::getShapeType() const {
+    return resultWithReadLock<ShapeType>([&] {
+        return _shapeType;
+    });
+}
+
+void ParticleEffectEntityItem::setCompoundShapeURL(const QString& compoundShapeURL) {
+    withWriteLock([&] {
+        _compoundShapeURL = compoundShapeURL;
+    });
+}
+
+QString ParticleEffectEntityItem::getCompoundShapeURL() const {
+    return resultWithReadLock<QString>([&] {
+        return _compoundShapeURL;
+    });
+}
+
 void ParticleEffectEntityItem::setMaxParticles(quint32 maxParticles) {
     withWriteLock([&] {
         _particleProperties.maxParticles = glm::clamp(maxParticles, MINIMUM_MAX_PARTICLES, MAXIMUM_MAX_PARTICLES);
diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h
index 0755d7868b..52f229201e 100644
--- a/libraries/entities/src/ParticleEffectEntityItem.h
+++ b/libraries/entities/src/ParticleEffectEntityItem.h
@@ -79,6 +79,7 @@ namespace particle {
     static const QString DEFAULT_TEXTURES = "";
     static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false;
     static const bool DEFAULT_ROTATE_WITH_ENTITY = false;
+    static const ShapeType DEFAULT_SHAPE_TYPE = ShapeType::SHAPE_TYPE_ELLIPSOID;
 
     template <typename T>
     struct Range {
@@ -255,7 +256,10 @@ public:
     float getAlphaSpread() const { return _particleProperties.alpha.gradient.spread; }
 
     void setShapeType(ShapeType type) override;
-    virtual ShapeType getShapeType() const override { return _shapeType; }
+    virtual ShapeType getShapeType() const override;
+
+    QString getCompoundShapeURL() const;
+    virtual void setCompoundShapeURL(const QString& url);
 
     virtual void debugDump() const override;
 
@@ -349,7 +353,8 @@ protected:
     PulsePropertyGroup _pulseProperties;
     bool _isEmitting { true };
 
-    ShapeType _shapeType { SHAPE_TYPE_NONE };
+    ShapeType _shapeType{ particle::DEFAULT_SHAPE_TYPE };
+    QString _compoundShapeURL { "" };
 };
 
 #endif // hifi_ParticleEffectEntityItem_h
diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp
index 98b18869fc..f243d59da0 100644
--- a/libraries/entities/src/ZoneEntityItem.cpp
+++ b/libraries/entities/src/ZoneEntityItem.cpp
@@ -13,7 +13,6 @@
 
 #include <glm/gtx/transform.hpp>
 #include <QDebug>
-#include <QUrlQuery>
 
 #include <ByteCountCoding.h>
 
@@ -463,9 +462,6 @@ void ZoneEntityItem::fetchCollisionGeometryResource() {
     if (hullURL.isEmpty()) {
         _shapeResource.reset();
     } else {
-        QUrlQuery queryArgs(hullURL);
-        queryArgs.addQueryItem("collision-hull", "");
-        hullURL.setQuery(queryArgs);
         _shapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
     }
 }

From 432a3f1610d0ccbe0004a0ab2fa14a3880727c56 Mon Sep 17 00:00:00 2001
From: SamGondelman <samuel@highfidelity.io>
Date: Tue, 19 Mar 2019 12:59:03 -0700
Subject: [PATCH 2/6] uniform sampling of ellipsoid points

---
 .../src/RenderableParticleEffectEntityItem.cpp      |  9 +++++----
 libraries/entities/src/ParticleEffectEntityItem.cpp | 13 +++++++++++++
 libraries/networking/src/udt/PacketHeaders.h        |  1 +
 3 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
index 2168347554..df393e691c 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
@@ -239,7 +239,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
             azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat();
         }
 
-        if (emitDimensions == Vectors::ZERO || shapeType == ShapeType::SHAPE_TYPE_NONE) {
+        if (emitDimensions == Vectors::ZERO) {
             // Point
             emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z;
         } else {
@@ -265,9 +265,10 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
                 default: {
                     float radiusScale = 1.0f;
                     if (emitRadiusStart < 1.0f) {
-                        float randRadius =
-                            emitRadiusStart + randFloatInRange(0.0f, particle::MAXIMUM_EMIT_RADIUS_START - emitRadiusStart);
-                        radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f);
+                        float innerRadiusCubed = emitRadiusStart * emitRadiusStart * emitRadiusStart;
+                        float outerRadiusCubed = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 3);
+                        float randRadiusCubed = randFloatInRange(innerRadiusCubed, outerRadiusCubed);
+                        radiusScale = std::cbrt(randRadiusCubed);
                     }
 
                     glm::vec3 radii = radiusScale * 0.5f * emitDimensions;
diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp
index 801cf56b32..b9f723d880 100644
--- a/libraries/entities/src/ParticleEffectEntityItem.cpp
+++ b/libraries/entities/src/ParticleEffectEntityItem.cpp
@@ -723,6 +723,19 @@ void ParticleEffectEntityItem::debugDump() const {
 }
 
 void ParticleEffectEntityItem::setShapeType(ShapeType type) {
+    switch (type) {
+        case SHAPE_TYPE_NONE:
+        case SHAPE_TYPE_HULL:
+        case SHAPE_TYPE_SIMPLE_HULL:
+        case SHAPE_TYPE_SIMPLE_COMPOUND:
+        case SHAPE_TYPE_STATIC_MESH:
+            // these types are unsupported for ParticleEffectEntity
+            type = particle::DEFAULT_SHAPE_TYPE;
+            break;
+        default:
+            break;
+    }
+
     withWriteLock([&] {
         if (type != _shapeType) {
             _shapeType = type;
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index 0ec7c40ca4..48aecbc791 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -266,6 +266,7 @@ enum class EntityVersion : PacketVersion {
     ModelScale,
     ReOrderParentIDProperties,
     CertificateTypeProperty,
+    ParticleShapeType,
 
     // Add new versions above here
     NUM_PACKET_TYPE,

From 194008c77a745e56b50128474a3baadebd231cd3 Mon Sep 17 00:00:00 2001
From: SamGondelman <samuel@highfidelity.io>
Date: Tue, 19 Mar 2019 14:14:37 -0700
Subject: [PATCH 3/6] box shapeType

---
 .../RenderableParticleEffectEntityItem.cpp    | 83 ++++++++++++-------
 .../entities/src/ParticleEffectEntityItem.cpp |  3 +
 2 files changed, 57 insertions(+), 29 deletions(-)

diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
index df393e691c..1d384cc3b5 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
@@ -98,12 +98,6 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
         _timeUntilNextEmit = 0;
         withWriteLock([&] {
             _particleProperties = newParticleProperties;
-            _shapeType = entity->getShapeType();
-            QString compoundShapeURL = entity->getCompoundShapeURL();
-            if (_compoundShapeURL != compoundShapeURL) {
-                _compoundShapeURL = compoundShapeURL;
-                fetchGeometryResource();
-            }
             if (!_prevEmitterShouldTrailInitialized) {
                 _prevEmitterShouldTrailInitialized = true;
                 _prevEmitterShouldTrail = _particleProperties.emission.shouldTrail;
@@ -113,6 +107,12 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
 
     withWriteLock([&] {
         _pulseProperties = entity->getPulseProperties();
+        _shapeType = entity->getShapeType();
+        QString compoundShapeURL = entity->getCompoundShapeURL();
+        if (_compoundShapeURL != compoundShapeURL) {
+            _compoundShapeURL = compoundShapeURL;
+            fetchGeometryResource();
+        }
     });
     _emitting = entity->getIsEmitting();
 
@@ -193,7 +193,28 @@ Item::Bound ParticleEffectEntityRenderer::getBound() {
     return _bound;
 }
 
-static const size_t VERTEX_PER_PARTICLE = 4;
+// FIXME: these methods assume uniform emitDimensions, need to importance sample based on dimensions
+float importanceSample2Dimension(float startDim) {
+    float dimension = 1.0f;
+    if (startDim < 1.0f) {
+        float innerDimensionSquared = startDim * startDim;
+        float outerDimensionSquared = 1.0f;  // pow(particle::MAXIMUM_EMIT_RADIUS_START, 2);
+        float randDimensionSquared = randFloatInRange(innerDimensionSquared, outerDimensionSquared);
+        dimension = std::cbrt(randDimensionSquared);
+    }
+    return dimension;
+}
+
+float importanceSample3DDimension(float startDim) {
+    float dimension = 1.0f;
+    if (startDim < 1.0f) {
+        float innerDimensionCubed = startDim * startDim * startDim;
+        float outerDimensionCubed = 1.0f;  // pow(particle::MAXIMUM_EMIT_RADIUS_START, 3);
+        float randDimensionCubed = randFloatInRange(innerDimensionCubed, outerDimensionCubed);
+        dimension = std::cbrt(randDimensionCubed);
+    }
+    return dimension;
+}
 
 ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties,
                                                                                        const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource) {
@@ -245,33 +266,35 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
         } else {
             glm::vec3 emitPosition;
             switch (shapeType) {
-                case ShapeType::SHAPE_TYPE_BOX:
+                case SHAPE_TYPE_BOX: {
+                    glm::vec3 dim = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions;
 
-                case ShapeType::SHAPE_TYPE_CAPSULE_X:
-                case ShapeType::SHAPE_TYPE_CAPSULE_Y:
-                case ShapeType::SHAPE_TYPE_CAPSULE_Z:
+                    int side = randIntInRange(0, 5);
+                    int axis = side % 3;
+                    float direction = side > 2 ? 1.0f : -1.0f;
 
-                case ShapeType::SHAPE_TYPE_CYLINDER_X:
-                case ShapeType::SHAPE_TYPE_CYLINDER_Y:
-                case ShapeType::SHAPE_TYPE_CYLINDER_Z:
+                    emitDirection[axis] = direction;
+                    emitPosition[axis] = direction * dim[axis];
+                    axis = (axis + 1) % 3;
+                    emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
+                    axis = (axis + 1) % 3;
+                    emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
+                    break;
+                }
 
-                case ShapeType::SHAPE_TYPE_CIRCLE:
-                case ShapeType::SHAPE_TYPE_PLANE:
+                case SHAPE_TYPE_CYLINDER_X:
+                case SHAPE_TYPE_CYLINDER_Y:
+                case SHAPE_TYPE_CYLINDER_Z:
 
-                case ShapeType::SHAPE_TYPE_COMPOUND:
+                case SHAPE_TYPE_CIRCLE:
+                case SHAPE_TYPE_PLANE:
 
-                case ShapeType::SHAPE_TYPE_SPHERE:
-                case ShapeType::SHAPE_TYPE_ELLIPSOID:
+                case SHAPE_TYPE_COMPOUND:
+
+                case SHAPE_TYPE_SPHERE:
+                case SHAPE_TYPE_ELLIPSOID:
                 default: {
-                    float radiusScale = 1.0f;
-                    if (emitRadiusStart < 1.0f) {
-                        float innerRadiusCubed = emitRadiusStart * emitRadiusStart * emitRadiusStart;
-                        float outerRadiusCubed = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 3);
-                        float randRadiusCubed = randFloatInRange(innerRadiusCubed, outerRadiusCubed);
-                        radiusScale = std::cbrt(randRadiusCubed);
-                    }
-
-                    glm::vec3 radii = radiusScale * 0.5f * emitDimensions;
+                    glm::vec3 radii = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions;
                     float x = radii.x * glm::cos(elevation) * glm::cos(azimuth);
                     float y = radii.y * glm::cos(elevation) * glm::sin(azimuth);
                     float z = radii.z * glm::sin(elevation);
@@ -279,6 +302,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
                     emitDirection = glm::normalize(glm::vec3(radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f,
                                                              radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f,
                                                              radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f));
+                    break;
                 }
             }
 
@@ -313,7 +337,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
 
     const auto& modelTransform = getModelTransform();
     if (_emitting && particleProperties.emitting() &&
-        (_shapeType != ShapeType::SHAPE_TYPE_COMPOUND || (_geometryResource && _geometryResource->isLoaded()))) {
+        (_shapeType != SHAPE_TYPE_COMPOUND || (_geometryResource && _geometryResource->isLoaded()))) {
         uint64_t emitInterval = particleProperties.emitIntervalUsecs();
         if (emitInterval > 0 && interval >= _timeUntilNextEmit) {
             auto timeRemaining = interval;
@@ -395,6 +419,7 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) {
     batch.setInputBuffer(0, _particleBuffer, 0, sizeof(GpuParticle));
 
     auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle);
+    static const size_t VERTEX_PER_PARTICLE = 4;
     batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE);
 }
 
diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp
index b9f723d880..5f285ca91b 100644
--- a/libraries/entities/src/ParticleEffectEntityItem.cpp
+++ b/libraries/entities/src/ParticleEffectEntityItem.cpp
@@ -725,6 +725,9 @@ void ParticleEffectEntityItem::debugDump() const {
 void ParticleEffectEntityItem::setShapeType(ShapeType type) {
     switch (type) {
         case SHAPE_TYPE_NONE:
+        case SHAPE_TYPE_CAPSULE_X:
+        case SHAPE_TYPE_CAPSULE_Y:
+        case SHAPE_TYPE_CAPSULE_Z:
         case SHAPE_TYPE_HULL:
         case SHAPE_TYPE_SIMPLE_HULL:
         case SHAPE_TYPE_SIMPLE_COMPOUND:

From 38864e5b6e581917cb1f172f39f6fde36ddcfd77 Mon Sep 17 00:00:00 2001
From: SamGondelman <samuel@highfidelity.io>
Date: Tue, 19 Mar 2019 18:04:32 -0700
Subject: [PATCH 4/6] plane, circle, cylinders

---
 .../RenderableParticleEffectEntityItem.cpp    | 47 +++++++++++++++++--
 1 file changed, 42 insertions(+), 5 deletions(-)

diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
index 1d384cc3b5..8883108f75 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
@@ -194,13 +194,13 @@ Item::Bound ParticleEffectEntityRenderer::getBound() {
 }
 
 // FIXME: these methods assume uniform emitDimensions, need to importance sample based on dimensions
-float importanceSample2Dimension(float startDim) {
+float importanceSample2DDimension(float startDim) {
     float dimension = 1.0f;
     if (startDim < 1.0f) {
         float innerDimensionSquared = startDim * startDim;
         float outerDimensionSquared = 1.0f;  // pow(particle::MAXIMUM_EMIT_RADIUS_START, 2);
         float randDimensionSquared = randFloatInRange(innerDimensionSquared, outerDimensionSquared);
-        dimension = std::cbrt(randDimensionSquared);
+        dimension = std::sqrt(randDimensionSquared);
     }
     return dimension;
 }
@@ -259,6 +259,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
         } else {
             azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat();
         }
+        // TODO: azimuth and elevation are only used for ellipsoids, but could be used for other shapes too
 
         if (emitDimensions == Vectors::ZERO) {
             // Point
@@ -284,10 +285,46 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
 
                 case SHAPE_TYPE_CYLINDER_X:
                 case SHAPE_TYPE_CYLINDER_Y:
-                case SHAPE_TYPE_CYLINDER_Z:
+                case SHAPE_TYPE_CYLINDER_Z: {
+                    glm::vec3 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * emitDimensions;
+                    int axis = shapeType - SHAPE_TYPE_CYLINDER_X;
 
-                case SHAPE_TYPE_CIRCLE:
-                case SHAPE_TYPE_PLANE:
+                    emitPosition[axis] = emitDimensions[axis] * randFloatInRange(-0.5f, 0.5f);
+                    emitDirection[axis] = 0.0f;
+                    axis = (axis + 1) % 3;
+                    emitPosition[axis] = radii[axis] * glm::cos(azimuth);
+                    emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f;
+                    axis = (axis + 1) % 3;
+                    emitPosition[axis] = radii[axis] * glm::sin(azimuth);
+                    emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f;
+                    emitDirection = glm::normalize(emitDirection);
+                    break;
+                }
+
+                case SHAPE_TYPE_CIRCLE: { // FIXME: SHAPE_TYPE_CIRCLE is not exposed to scripts in buildStringToShapeTypeLookup()
+                    glm::vec2 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z);
+                    float x = radii.x * glm::cos(azimuth);
+                    float z = radii.y * glm::sin(azimuth);
+                    emitPosition = glm::vec3(x, 0.0f, z);
+                    emitDirection = Vectors::UP;
+                    break;
+                }
+                case SHAPE_TYPE_PLANE: {
+                    glm::vec2 dim = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z);
+
+                    int side = randIntInRange(0, 3);
+                    int axis = side % 2;
+                    float direction = side > 1 ? 1.0f : -1.0f;
+
+                    glm::vec2 pos;
+                    pos[axis] = direction * dim[axis];
+                    axis = (axis + 1) % 2;
+                    pos[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f);
+
+                    emitPosition = glm::vec3(pos.x, 0.0f, pos.y);
+                    emitDirection = Vectors::UP;
+                    break;
+                }
 
                 case SHAPE_TYPE_COMPOUND:
 

From 3ff0770441a2ce24063063eff9b07264715bc5f9 Mon Sep 17 00:00:00 2001
From: SamGondelman <samuel@highfidelity.io>
Date: Wed, 20 Mar 2019 21:14:54 -0700
Subject: [PATCH 5/6] model emitters!

---
 .../RenderableParticleEffectEntityItem.cpp    | 173 +++++++++++++++++-
 .../src/RenderableParticleEffectEntityItem.h  |  12 +-
 .../entities/src/EntityItemProperties.cpp     |  21 ++-
 libraries/entities/src/LineEntityItem.h       |   2 -
 libraries/entities/src/ModelEntityItem.h      |   2 +-
 libraries/entities/src/ShapeEntityItem.h      |   2 +-
 libraries/entities/src/ZoneEntityItem.cpp     |   2 +-
 libraries/entities/src/ZoneEntityItem.h       |   2 +-
 libraries/shared/src/GeometryUtil.cpp         |   6 +
 libraries/shared/src/GeometryUtil.h           |   1 +
 .../system/assets/data/createAppTooltips.json |   8 +
 scripts/system/html/js/entityProperties.js    |  15 ++
 12 files changed, 222 insertions(+), 24 deletions(-)

diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
index 8883108f75..d517ecd026 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
@@ -1,4 +1,4 @@
-//
+//
 //  RenderableParticleEffectEntityItem.cpp
 //  interface/src
 //
@@ -14,6 +14,8 @@
 #include <GeometryCache.h>
 #include <shaders/Shaders.h>
 
+#include <glm/gtx/transform.hpp>
+
 using namespace render;
 using namespace render::entities;
 
@@ -111,6 +113,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
         QString compoundShapeURL = entity->getCompoundShapeURL();
         if (_compoundShapeURL != compoundShapeURL) {
             _compoundShapeURL = compoundShapeURL;
+            _hasComputedTriangles = false;
             fetchGeometryResource();
         }
     });
@@ -217,7 +220,8 @@ float importanceSample3DDimension(float startDim) {
 }
 
 ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties,
-                                                                                       const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource) {
+                                                                                       const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource,
+                                                                                       const TriangleInfo& triangleInfo) {
     CpuParticle particle;
 
     const auto& accelerationSpread = particleProperties.emission.acceleration.spread;
@@ -259,7 +263,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
         } else {
             azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat();
         }
-        // TODO: azimuth and elevation are only used for ellipsoids, but could be used for other shapes too
+        // TODO: azimuth and elevation are only used for ellipsoids/circles, but could be used for other shapes too
 
         if (emitDimensions == Vectors::ZERO) {
             // Point
@@ -301,7 +305,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
                     break;
                 }
 
-                case SHAPE_TYPE_CIRCLE: { // FIXME: SHAPE_TYPE_CIRCLE is not exposed to scripts in buildStringToShapeTypeLookup()
+                case SHAPE_TYPE_CIRCLE: {
                     glm::vec2 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z);
                     float x = radii.x * glm::cos(azimuth);
                     float z = radii.y * glm::sin(azimuth);
@@ -326,7 +330,43 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
                     break;
                 }
 
-                case SHAPE_TYPE_COMPOUND:
+                case SHAPE_TYPE_COMPOUND: {
+                    // if we get here we know that geometryResource is loaded
+
+                    size_t index = randFloat() * triangleInfo.totalSamples;
+                    Triangle triangle;
+                    for (size_t i = 0; i < triangleInfo.samplesPerTriangle.size(); i++) {
+                        size_t numSamples = triangleInfo.samplesPerTriangle[i];
+                        if (index < numSamples) {
+                            triangle = triangleInfo.triangles[i];
+                            break;
+                        }
+                        index -= numSamples;
+                    }
+
+                    float edgeLength1 = glm::length(triangle.v1 - triangle.v0);
+                    float edgeLength2 = glm::length(triangle.v2 - triangle.v1);
+                    float edgeLength3 = glm::length(triangle.v0 - triangle.v2);
+
+                    float perimeter = edgeLength1 + edgeLength2 + edgeLength3;
+                    float fraction1 = randFloatInRange(0.0f, 1.0f);
+                    float fractionEdge1 = glm::min(fraction1 * perimeter / edgeLength1, 1.0f);
+                    float fraction2 = fraction1 - edgeLength1 / perimeter;
+                    float fractionEdge2 = glm::clamp(fraction2 * perimeter / edgeLength2, 0.0f, 1.0f);
+                    float fraction3 = fraction2 - edgeLength2 / perimeter;
+                    float fractionEdge3 = glm::clamp(fraction3 * perimeter / edgeLength3, 0.0f, 1.0f);
+
+                    float dim = importanceSample2DDimension(emitRadiusStart);
+                    triangle = triangle * (glm::scale(emitDimensions) * triangleInfo.transform);
+                    glm::vec3 center = (triangle.v0 + triangle.v1 + triangle.v2) / 3.0f;
+                    glm::vec3 v0 = (dim * (triangle.v0 - center)) + center;
+                    glm::vec3 v1 = (dim * (triangle.v1 - center)) + center;
+                    glm::vec3 v2 = (dim * (triangle.v2 - center)) + center;
+
+                    emitPosition = glm::mix(v0, glm::mix(v1, glm::mix(v2, v0, fractionEdge3), fractionEdge2), fractionEdge1);
+                    emitDirection = triangle.getNormal();
+                    break;
+                }
 
                 case SHAPE_TYPE_SPHERE:
                 case SHAPE_TYPE_ELLIPSOID:
@@ -374,13 +414,16 @@ void ParticleEffectEntityRenderer::stepSimulation() {
 
     const auto& modelTransform = getModelTransform();
     if (_emitting && particleProperties.emitting() &&
-        (_shapeType != SHAPE_TYPE_COMPOUND || (_geometryResource && _geometryResource->isLoaded()))) {
+        (shapeType != SHAPE_TYPE_COMPOUND || (geometryResource && geometryResource->isLoaded()))) {
         uint64_t emitInterval = particleProperties.emitIntervalUsecs();
         if (emitInterval > 0 && interval >= _timeUntilNextEmit) {
             auto timeRemaining = interval;
             while (timeRemaining > _timeUntilNextEmit) {
+                if (_shapeType == SHAPE_TYPE_COMPOUND && !_hasComputedTriangles) {
+                    computeTriangles(geometryResource->getHFMModel());
+                }
                 // emit particle
-                _cpuParticles.push_back(createParticle(now, modelTransform, particleProperties, shapeType, geometryResource));
+                _cpuParticles.push_back(createParticle(now, modelTransform, particleProperties, shapeType, geometryResource, _triangleInfo));
                 _timeUntilNextEmit = emitInterval;
                 if (emitInterval < timeRemaining) {
                     timeRemaining -= emitInterval;
@@ -467,4 +510,120 @@ void ParticleEffectEntityRenderer::fetchGeometryResource() {
     } else {
         _geometryResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
     }
+}
+
+// FIXME: this is very similar to Model::calculateTriangleSets
+void ParticleEffectEntityRenderer::computeTriangles(const hfm::Model& hfmModel) {
+    PROFILE_RANGE(render, __FUNCTION__);
+
+    int numberOfMeshes = hfmModel.meshes.size();
+
+    _hasComputedTriangles = true;
+    _triangleInfo.triangles.clear();
+    _triangleInfo.samplesPerTriangle.clear();
+
+    std::vector<float> areas;
+    float minArea = FLT_MAX;
+    AABox bounds;
+
+    for (int i = 0; i < numberOfMeshes; i++) {
+        const HFMMesh& mesh = hfmModel.meshes.at(i);
+
+        const int numberOfParts = mesh.parts.size();
+        for (int j = 0; j < numberOfParts; j++) {
+            const HFMMeshPart& part = mesh.parts.at(j);
+
+            const int INDICES_PER_TRIANGLE = 3;
+            const int INDICES_PER_QUAD = 4;
+            const int TRIANGLES_PER_QUAD = 2;
+
+            // tell our triangleSet how many triangles to expect.
+            int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD;
+            int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE;
+            int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris;
+            _triangleInfo.triangles.reserve(_triangleInfo.triangles.size() + totalTriangles);
+            areas.reserve(areas.size() + totalTriangles);
+
+            auto meshTransform = hfmModel.offset * mesh.modelTransform;
+
+            if (part.quadIndices.size() > 0) {
+                int vIndex = 0;
+                for (int q = 0; q < numberOfQuads; q++) {
+                    int i0 = part.quadIndices[vIndex++];
+                    int i1 = part.quadIndices[vIndex++];
+                    int i2 = part.quadIndices[vIndex++];
+                    int i3 = part.quadIndices[vIndex++];
+
+                    // track the model space version... these points will be transformed by the FST's offset, 
+                    // which includes the scaling, rotation, and translation specified by the FST/FBX, 
+                    // this can't change at runtime, so we can safely store these in our TriangleSet
+                    glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f));
+                    glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f));
+                    glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f));
+                    glm::vec3 v3 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i3], 1.0f));
+
+                    Triangle tri1 = { v0, v1, v3 };
+                    Triangle tri2 = { v1, v2, v3 };
+                    _triangleInfo.triangles.push_back(tri1);
+                    _triangleInfo.triangles.push_back(tri2);
+
+                    float area1 = tri1.getArea();
+                    areas.push_back(area1);
+                    if (area1 > EPSILON) {
+                        minArea = std::min(minArea, area1);
+                    }
+
+                    float area2 = tri2.getArea();
+                    areas.push_back(area2);
+                    if (area2 > EPSILON) {
+                        minArea = std::min(minArea, area2);
+                    }
+
+                    bounds += v0;
+                    bounds += v1;
+                    bounds += v2;
+                    bounds += v3;
+                }
+            }
+
+            if (part.triangleIndices.size() > 0) {
+                int vIndex = 0;
+                for (int t = 0; t < numberOfTris; t++) {
+                    int i0 = part.triangleIndices[vIndex++];
+                    int i1 = part.triangleIndices[vIndex++];
+                    int i2 = part.triangleIndices[vIndex++];
+
+                    // track the model space version... these points will be transformed by the FST's offset, 
+                    // which includes the scaling, rotation, and translation specified by the FST/FBX, 
+                    // this can't change at runtime, so we can safely store these in our TriangleSet
+                    glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f));
+                    glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f));
+                    glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f));
+
+                    Triangle tri = { v0, v1, v2 };
+                    _triangleInfo.triangles.push_back(tri);
+
+                    float area = tri.getArea();
+                    areas.push_back(area);
+                    if (area > EPSILON) {
+                        minArea = std::min(minArea, area);
+                    }
+
+                    bounds += v0;
+                    bounds += v1;
+                    bounds += v2;
+                }
+            }
+        }
+    }
+
+    _triangleInfo.totalSamples = 0;
+    for (auto& area : areas) {
+        size_t numSamples = area / minArea;
+        _triangleInfo.samplesPerTriangle.push_back(numSamples);
+        _triangleInfo.totalSamples += numSamples;
+    }
+
+    glm::vec3 scale = bounds.getScale();
+    _triangleInfo.transform = glm::scale(1.0f / scale) * glm::translate(-bounds.calcCenter());
 }
\ No newline at end of file
diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h
index 4a4e5e5cbc..d13c966e96 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h
@@ -81,8 +81,18 @@ private:
         glm::vec2 spare;
     };
 
+    void computeTriangles(const hfm::Model& hfmModel);
+    bool _hasComputedTriangles{ false };
+    struct TriangleInfo {
+        std::vector<Triangle> triangles;
+        std::vector<size_t> samplesPerTriangle;
+        size_t totalSamples;
+        glm::mat4 transform;
+    } _triangleInfo;
+
     static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties,
-                                      const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource);
+                                      const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource,
+                                      const TriangleInfo& triangleInfo);
     void stepSimulation();
 
     particle::Properties _particleProperties;
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index 0a6875b63d..5958af66dd 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -127,6 +127,7 @@ void buildStringToShapeTypeLookup() {
     addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
     addShapeType(SHAPE_TYPE_STATIC_MESH);
     addShapeType(SHAPE_TYPE_ELLIPSOID);
+    addShapeType(SHAPE_TYPE_CIRCLE);
 }
 
 QHash<QString, MaterialMappingMode> stringToMaterialMappingModeLookup;
@@ -1121,21 +1122,21 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
  *     Particles are emitted from the portion of the shape that lies between <code>emitRadiusStart</code> and the 
  *     shape's surface.
  * @property {number} polarStart=0 - The angle in radians from the entity's local z-axis at which particles start being emitted 
- *     within the ellipsoid; range <code>0</code> &ndash; <code>Math.PI</code>. Particles are emitted from the portion of the 
- *     ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.  Only used if <code>shapeType</code> is
- *     <code>ellipsoid</code>.
+ *     within the shape; range <code>0</code> &ndash; <code>Math.PI</code>. Particles are emitted from the portion of the 
+ *     shape that lies between <code>polarStart<code> and <code>polarFinish</code>.  Only used if <code>shapeType</code> is
+ *     <code>ellipsoid</code> or <code>sphere</code>.
  * @property {number} polarFinish=0 - The angle in radians from the entity's local z-axis at which particles stop being emitted 
- *     within the ellipsoid; range <code>0</code> &ndash; <code>Math.PI</code>. Particles are emitted from the portion of the 
- *     ellipsoid that lies between <code>polarStart<code> and <code>polarFinish</code>.  Only used if <code>shapeType</code> is
- *     <code>ellipsoid</code>.
+ *     within the shape; range <code>0</code> &ndash; <code>Math.PI</code>. Particles are emitted from the portion of the 
+ *     shape that lies between <code>polarStart<code> and <code>polarFinish</code>.  Only used if <code>shapeType</code> is
+ *     <code>ellipsoid</code> or <code>sphere</code>.
  * @property {number} azimuthStart=-Math.PI - The angle in radians from the entity's local x-axis about the entity's local 
  *     z-axis at which particles start being emitted; range <code>-Math.PI</code> &ndash; <code>Math.PI</code>. Particles are 
- *     emitted from the portion of the ellipsoid that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
- *     Only used if <code>shapeType</code> is <code>ellipsoid</code>.
+ *     emitted from the portion of the shape that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
+ *     Only used if <code>shapeType</code> is <code>ellipsoid</code>, <code>sphere</code>, or <code>circle</code>.
  * @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local
  *     z-axis at which particles stop being emitted; range <code>-Math.PI</code> &ndash; <code>Math.PI</code>. Particles are
- *     emitted from the portion of the ellipsoid that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
- *     Only used if <code>shapeType</code> is <code>ellipsoid</code>.
+ *     emitted from the portion of the shape that lies between <code>azimuthStart<code> and <code>azimuthFinish</code>.
+ *     Only used if <code>shapeType</code> is <code>ellipsoid</code>, <code>sphere</code>, or <code>circle</code>..
  *
  * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency,
  *     use PNG format.
diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h
index 86ca6065bb..098183299f 100644
--- a/libraries/entities/src/LineEntityItem.h
+++ b/libraries/entities/src/LineEntityItem.h
@@ -49,8 +49,6 @@ class LineEntityItem : public EntityItem {
 
     QVector<glm::vec3> getLinePoints() const;
 
-    virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; }
-
     // never have a ray intersection pick a LineEntityItem.
     virtual bool supportsDetailedIntersection() const override { return true; }
     virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h
index 08468617ba..8f5e24ad76 100644
--- a/libraries/entities/src/ModelEntityItem.h
+++ b/libraries/entities/src/ModelEntityItem.h
@@ -175,7 +175,7 @@ protected:
 
     QString _textures;
 
-    ShapeType _shapeType = SHAPE_TYPE_NONE;
+    ShapeType _shapeType { SHAPE_TYPE_NONE } ;
 
 private:
     uint64_t _lastAnimated{ 0 };
diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h
index 363a7f39d1..fc590e06a4 100644
--- a/libraries/entities/src/ShapeEntityItem.h
+++ b/libraries/entities/src/ShapeEntityItem.h
@@ -112,7 +112,7 @@ protected:
     //! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain
     //! prior functionality where new or unsupported shapes are treated as
     //! ellipsoids.
-    ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID };
+    ShapeType _collisionShapeType { ShapeType::SHAPE_TYPE_ELLIPSOID };
 };
 
 #endif // hifi_ShapeEntityItem_h
diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp
index f243d59da0..0771d9ad54 100644
--- a/libraries/entities/src/ZoneEntityItem.cpp
+++ b/libraries/entities/src/ZoneEntityItem.cpp
@@ -352,7 +352,7 @@ bool ZoneEntityItem::contains(const glm::vec3& point) const {
 
             Extents meshExtents = hfmModel.getMeshExtents();
             glm::vec3 meshExtentsDiagonal = meshExtents.maximum - meshExtents.minimum;
-            glm::vec3 offset = -meshExtents.minimum- (meshExtentsDiagonal * getRegistrationPoint());
+            glm::vec3 offset = -meshExtents.minimum - (meshExtentsDiagonal * getRegistrationPoint());
             glm::vec3 scale(getScaledDimensions() / meshExtentsDiagonal);
 
             glm::mat4 hfmToEntityMatrix = glm::scale(scale) * glm::translate(offset);
diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h
index df6ce50fd6..69e3227135 100644
--- a/libraries/entities/src/ZoneEntityItem.h
+++ b/libraries/entities/src/ZoneEntityItem.h
@@ -133,7 +133,7 @@ protected:
     KeyLightPropertyGroup _keyLightProperties;
     AmbientLightPropertyGroup _ambientLightProperties;
 
-    ShapeType _shapeType = DEFAULT_SHAPE_TYPE;
+    ShapeType _shapeType { DEFAULT_SHAPE_TYPE };
     QString _compoundShapeURL;
 
     // The following 3 values are the defaults for zone creation
diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp
index 3f2f7cd7fb..b6fca03403 100644
--- a/libraries/shared/src/GeometryUtil.cpp
+++ b/libraries/shared/src/GeometryUtil.cpp
@@ -358,6 +358,12 @@ glm::vec3 Triangle::getNormal() const {
     return glm::normalize(glm::cross(edge1, edge2));
 }
 
+float Triangle::getArea() const {
+    glm::vec3 edge1 = v1 - v0;
+    glm::vec3 edge2 = v2 - v0;
+    return 0.5f * glm::length(glm::cross(edge1, edge2));
+}
+
 Triangle Triangle::operator*(const glm::mat4& transform) const {
     return {
         glm::vec3(transform * glm::vec4(v0, 1.0f)),
diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h
index 8ec75f71bd..04c54fc32e 100644
--- a/libraries/shared/src/GeometryUtil.h
+++ b/libraries/shared/src/GeometryUtil.h
@@ -125,6 +125,7 @@ public:
     glm::vec3 v1;
     glm::vec3 v2;
     glm::vec3 getNormal() const;
+    float getArea() const;
     Triangle operator*(const glm::mat4& transform) const;
 };
 
diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json
index 7201cdecad..73c7504089 100644
--- a/scripts/system/assets/data/createAppTooltips.json
+++ b/scripts/system/assets/data/createAppTooltips.json
@@ -227,6 +227,14 @@
     "speedSpread": {
         "tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds."
     },
+    "particleShapeType": {
+        "tooltip": "The shape of the surface from which to emit particles.",
+        "jsPropertyName": "shapeType"
+    },
+    "particleCompoundShapeURL": {
+        "tooltip": "The model file to use for the particle emitter if Shape Type is \"Use Compound Shape URL\".",
+        "jsPropertyName": "compoundShapeURL"
+    },
     "emitDimensions": {
         "tooltip": "The outer limit radius in dimensions that the particles can be emitted from."
     },
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js
index f501df7933..15ab40e5ea 100644
--- a/scripts/system/html/js/entityProperties.js
+++ b/scripts/system/html/js/entityProperties.js
@@ -807,6 +807,21 @@ const GROUPS = [
                 decimals: 2,
                 propertyID: "speedSpread",
             },
+            {
+                label: "Shape Type",
+                type: "dropdown",
+                options: { "box": "Box", "ellipsoid": "Ellipsoid", 
+                           "cylinder-y": "Cylinder", "circle": "Circle", "plane": "Plane",
+                           "compound": "Use Compound Shape URL" },
+                propertyID: "particleShapeType",
+                propertyName: "shapeType",
+            },
+            {
+                label: "Compound Shape URL",
+                type: "string",
+                propertyID: "particleCompoundShapeURL",
+                propertyName: "compoundShapeURL",
+            },
             {
                 label: "Emit Dimensions",
                 type: "vec3",

From 6cc95fe8ac4f2e8e804add3686e319066abd8140 Mon Sep 17 00:00:00 2001
From: SamGondelman <samuel_gondelman@alumni.brown.edu>
Date: Wed, 27 Mar 2019 15:46:25 -0700
Subject: [PATCH 6/6] CR

---
 libraries/entities/src/ModelEntityItem.h            | 2 +-
 libraries/entities/src/ParticleEffectEntityItem.cpp | 5 +----
 2 files changed, 2 insertions(+), 5 deletions(-)

diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h
index 8f5e24ad76..d532fefe7e 100644
--- a/libraries/entities/src/ModelEntityItem.h
+++ b/libraries/entities/src/ModelEntityItem.h
@@ -175,7 +175,7 @@ protected:
 
     QString _textures;
 
-    ShapeType _shapeType { SHAPE_TYPE_NONE } ;
+    ShapeType _shapeType { SHAPE_TYPE_NONE };
 
 private:
     uint64_t _lastAnimated{ 0 };
diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp
index 5f285ca91b..12119f1466 100644
--- a/libraries/entities/src/ParticleEffectEntityItem.cpp
+++ b/libraries/entities/src/ParticleEffectEntityItem.cpp
@@ -740,10 +740,7 @@ void ParticleEffectEntityItem::setShapeType(ShapeType type) {
     }
 
     withWriteLock([&] {
-        if (type != _shapeType) {
-            _shapeType = type;
-            _flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
-        }
+        _shapeType = type;
     });
 }