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> – <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> – <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> – <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> – <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> – <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> – <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> – <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> – <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> – <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> – <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> – <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> – <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; }); }