From 5cc0b45850fa489dc0439a2a1c40c4f5a3089bc8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 15 Jul 2015 18:27:39 -0700 Subject: [PATCH] Improved ParticleEffectEntityItem rendering and updates * Created custom pipelines and shaders for untextured and textured particle rendering. * Created custom render payload for particles * Moved all particle updates into simulation rather then render. * Uses pendingChanges.updateItem lambda to update the playload with new data for rendering. * ParticleEffectEntityItem now updates its dimensions properly, based on emitter properties. * Bug fix for dt not accumulating properly, during gaps between updates. now we just update all the time. (super cheap tho, if there are no particles animating) --- examples/particles.js | 7 +- libraries/entities-renderer/CMakeLists.txt | 2 + .../RenderableParticleEffectEntityItem.cpp | 327 ++++++++++++++---- .../src/RenderableParticleEffectEntityItem.h | 25 +- .../src/textured_particle.slf | 20 ++ .../src/textured_particle.slv | 28 ++ .../src/untextured_particle.slf | 16 + .../src/untextured_particle.slv | 24 ++ .../entities/src/ParticleEffectEntityItem.cpp | 80 ++++- .../entities/src/ParticleEffectEntityItem.h | 14 +- 10 files changed, 457 insertions(+), 86 deletions(-) create mode 100644 libraries/entities-renderer/src/textured_particle.slf create mode 100644 libraries/entities-renderer/src/textured_particle.slv create mode 100644 libraries/entities-renderer/src/untextured_particle.slf create mode 100644 libraries/entities-renderer/src/untextured_particle.slv diff --git a/examples/particles.js b/examples/particles.js index c26458a9af..deb6228fff 100644 --- a/examples/particles.js +++ b/examples/particles.js @@ -44,6 +44,7 @@ emitStrength: emitStrength, emitDirection: emitDirection, color: color, + lifespan: 1.0, visible: true, locked: false }); @@ -67,13 +68,13 @@ var objs = []; function Init() { objs.push(new TestBox()); - objs.push(new TestFx({ red: 255, blue: 0, green: 0 }, + objs.push(new TestFx({ red: 255, green: 0, blue: 0 }, { x: 0.5, y: 1.0, z: 0.0 }, 100, 3, 1)); - objs.push(new TestFx({ red: 0, blue: 255, green: 0 }, + objs.push(new TestFx({ red: 0, green: 255, blue: 0 }, { x: 0, y: 1, z: 0 }, 1000, 5, 0.5)); - objs.push(new TestFx({ red: 0, blue: 0, green: 255 }, + objs.push(new TestFx({ red: 0, green: 0, blue: 255 }, { x: -0.5, y: 1, z: 0 }, 100, 3, 1)); } diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 810d5f5843..e9adf2b750 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME entities-renderer) +AUTOSCRIBE_SHADER_LIB(gpu model render) + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Widgets OpenGL Network Script) diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 48ac83dfc2..738a150dc5 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -14,22 +14,141 @@ #include #include #include +#include #include "EntitiesRendererLogging.h" #include "RenderableParticleEffectEntityItem.h" +#include "untextured_particle_vert.h" +#include "untextured_particle_frag.h" +#include "textured_particle_vert.h" +#include "textured_particle_frag.h" + +class ParticlePayload { +public: + typedef render::Payload Payload; + typedef Payload::DataPointer Pointer; + typedef RenderableParticleEffectEntityItem::Vertex Vertex; + + ParticlePayload() : _vertexFormat(std::make_shared()), + _vertexBuffer(std::make_shared()), + _indexBuffer(std::make_shared()) { + _vertexFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element::VEC3F_XYZ, 0); + _vertexFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), offsetof(Vertex, uv)); + _vertexFormat->setAttribute(gpu::Stream::COLOR, 0, gpu::Element::COLOR_RGBA_32, offsetof(Vertex, rgba)); + } + + void setPipeline(gpu::PipelinePointer pipeline) { _pipeline = pipeline; } + const gpu::PipelinePointer& getPipeline() const { return _pipeline; } + + const Transform& getModelTransform() const { return _modelTransform; } + void setModelTransform(const Transform& modelTransform) { _modelTransform = modelTransform; } + + const AABox& getBound() const { return _bound; } + void setBound(AABox& bound) { _bound = bound; } + + gpu::BufferPointer getVertexBuffer() { return _vertexBuffer; } + const gpu::BufferPointer& getVertexBuffer() const { return _vertexBuffer; } + + gpu::BufferPointer getIndexBuffer() { return _indexBuffer; } + const gpu::BufferPointer& getIndexBuffer() const { return _indexBuffer; } + + void setTexture(gpu::TexturePointer texture) { _texture = texture; } + const gpu::TexturePointer& getTexture() const { return _texture; } + + bool getVisibleFlag() const { return _visibleFlag; } + void setVisibleFlag(bool visibleFlag) { _visibleFlag = visibleFlag; } + + void render(RenderArgs* args) const { + assert(_pipeline); + + gpu::Batch& batch = *args->_batch; + batch.setPipeline(_pipeline); + + if (_texture) { + batch.setResourceTexture(0, _texture); + } + + batch.setModelTransform(_modelTransform); + batch.setInputFormat(_vertexFormat); + batch.setInputBuffer(0, _vertexBuffer, 0, sizeof(Vertex)); + batch.setIndexBuffer(gpu::UINT16, _indexBuffer, 0); + + auto numIndices = _indexBuffer->getSize() / sizeof(uint16_t); + batch.drawIndexed(gpu::TRIANGLES, numIndices); + } + +protected: + Transform _modelTransform; + AABox _bound; + gpu::PipelinePointer _pipeline; + gpu::Stream::FormatPointer _vertexFormat; + gpu::BufferPointer _vertexBuffer; + gpu::BufferPointer _indexBuffer; + gpu::TexturePointer _texture; + bool _visibleFlag = true; +}; + +namespace render { + template <> + const ItemKey payloadGetKey(const ParticlePayload::Pointer& payload) { + if (payload->getVisibleFlag()) { + return ItemKey::Builder::transparentShape(); + } else { + return ItemKey::Builder().withInvisible().build(); + } + } + + template <> + const Item::Bound payloadGetBound(const ParticlePayload::Pointer& payload) { + return payload->getBound(); + } + + template <> + void payloadRender(const ParticlePayload::Pointer& payload, RenderArgs* args) { + payload->render(args); + } +} + +gpu::PipelinePointer RenderableParticleEffectEntityItem::_texturedPipeline; +gpu::PipelinePointer RenderableParticleEffectEntityItem::_untexturedPipeline; + EntityItemPointer RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return std::make_shared(entityID, properties); } RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : ParticleEffectEntityItem(entityItemID, properties) { - _cacheID = DependencyManager::get()->allocateID(); + + // lazy creation of particle system pipeline + if (!_untexturedPipeline && !_texturedPipeline) { + createPipelines(); + } } -void RenderableParticleEffectEntityItem::render(RenderArgs* args) { - Q_ASSERT(getType() == EntityTypes::ParticleEffect); - PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render"); +bool RenderableParticleEffectEntityItem::addToScene(EntityItemPointer self, + render::ScenePointer scene, + render::PendingChanges& pendingChanges) { + + auto particlePayload = std::shared_ptr(new ParticlePayload()); + particlePayload->setPipeline(_untexturedPipeline); + _renderItemId = scene->allocateID(); + auto renderData = ParticlePayload::Pointer(particlePayload); + auto renderPayload = render::PayloadPointer(new ParticlePayload::Payload(renderData)); + pendingChanges.resetItem(_renderItemId, renderPayload); + _scene = scene; + return true; +} + +void RenderableParticleEffectEntityItem::removeFromScene(EntityItemPointer self, + render::ScenePointer scene, + render::PendingChanges& pendingChanges) { + pendingChanges.removeItem(_renderItemId); + _scene = nullptr; +}; + +void RenderableParticleEffectEntityItem::update(const quint64& now) { + ParticleEffectEntityItem::update(now); if (_texturesChangedFlag) { if (_textures.isEmpty()) { @@ -42,71 +161,151 @@ void RenderableParticleEffectEntityItem::render(RenderArgs* args) { _texturesChangedFlag = false; } - bool textured = _texture && _texture->isLoaded(); - updateQuads(args, textured); - - Q_ASSERT(args->_batch); - gpu::Batch& batch = *args->_batch; - if (textured) { - batch.setResourceTexture(0, _texture->getGPUTexture()); - } - batch.setModelTransform(getTransformToCenter()); - DependencyManager::get()->bindSimpleProgram(batch, textured); - DependencyManager::get()->renderVertices(batch, gpu::QUADS, _cacheID); -}; + updateRenderItem(); +} static glm::vec3 zSortAxis; static bool zSort(const glm::vec3& rhs, const glm::vec3& lhs) { return glm::dot(rhs, ::zSortAxis) > glm::dot(lhs, ::zSortAxis); } -void RenderableParticleEffectEntityItem::updateQuads(RenderArgs* args, bool textured) { - float particleRadius = getParticleRadius(); - glm::vec4 particleColor(toGlm(getXColor()), getLocalRenderAlpha()); - - glm::vec3 upOffset = args->_viewFrustum->getUp() * particleRadius; - glm::vec3 rightOffset = args->_viewFrustum->getRight() * particleRadius; - - QVector vertices; - QVector positions; - QVector textureCoords; - vertices.reserve(getLivingParticleCount() * VERTS_PER_PARTICLE); - - if (textured) { - textureCoords.reserve(getLivingParticleCount() * VERTS_PER_PARTICLE); - } - positions.reserve(getLivingParticleCount()); - - - for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { - positions.append(_particlePositions[i]); - if (textured) { - textureCoords.append(glm::vec2(0, 1)); - textureCoords.append(glm::vec2(1, 1)); - textureCoords.append(glm::vec2(1, 0)); - textureCoords.append(glm::vec2(0, 0)); - } - } - - // sort particles back to front - ::zSortAxis = args->_viewFrustum->getDirection(); - qSort(positions.begin(), positions.end(), zSort); - - for (int i = 0; i < positions.size(); i++) { - glm::vec3 pos = (textured) ? positions[i] : _particlePositions[i]; - - // generate corners of quad aligned to face the camera. - vertices.append(pos + rightOffset + upOffset); - vertices.append(pos - rightOffset + upOffset); - vertices.append(pos - rightOffset - upOffset); - vertices.append(pos + rightOffset - upOffset); - - } - - if (textured) { - DependencyManager::get()->updateVertices(_cacheID, vertices, textureCoords, particleColor); - } else { - DependencyManager::get()->updateVertices(_cacheID, vertices, particleColor); - } +uint32_t toRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return ((uint32_t)r | (uint32_t)g << 8 | (uint32_t)b << 16 | (uint32_t)a << 24); } +void RenderableParticleEffectEntityItem::updateRenderItem() { + + if (!_scene) + return; + + float particleRadius = getParticleRadius(); + auto xcolor = getXColor(); + auto alpha = (uint8_t)(glm::clamp(getLocalRenderAlpha(), 0.0f, 1.0f) * 255.0f); + auto rgba = toRGBA(xcolor.red, xcolor.green, xcolor.blue, alpha); + + // make a copy of each particle position + std::vector positions; + positions.reserve(getLivingParticleCount()); + for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { + positions.push_back(_particlePositions[i]); + } + + // sort particles back to front + // NOTE: this is view frustum might be one frame out of date. + auto frustum = AbstractViewStateInterface::instance()->getCurrentViewFrustum(); + ::zSortAxis = frustum->getDirection(); + qSort(positions.begin(), positions.end(), zSort); + + // allocate vertices + _vertices.clear(); + + // build vertices from particle positions + const glm::vec3 upOffset = frustum->getUp() * particleRadius; + const glm::vec3 rightOffset = frustum->getRight() * particleRadius; + for (auto&& pos : positions) { + // generate corners of quad aligned to face the camera. + _vertices.emplace_back(pos + rightOffset + upOffset, glm::vec2(1.0f, 1.0f), rgba); + _vertices.emplace_back(pos - rightOffset + upOffset, glm::vec2(0.0f, 1.0f), rgba); + _vertices.emplace_back(pos - rightOffset - upOffset, glm::vec2(0.0f, 0.0f), rgba); + _vertices.emplace_back(pos + rightOffset - upOffset, glm::vec2(1.0f, 0.0f), rgba); + } + + render::PendingChanges pendingChanges; + pendingChanges.updateItem(_renderItemId, [&](ParticlePayload& payload) { + + // update vertex buffer + auto vertexBuffer = payload.getVertexBuffer(); + size_t numBytes = sizeof(Vertex) * _vertices.size(); + vertexBuffer->resize(numBytes); + gpu::Byte* data = vertexBuffer->editData(); + memcpy(data, &(_vertices[0]), numBytes); + + // FIXME, don't update index buffer if num particles has not changed. + // update index buffer + auto indexBuffer = payload.getIndexBuffer(); + auto numQuads = (_vertices.size() / 4); + numBytes = sizeof(uint16_t) * numQuads * 6; + indexBuffer->resize(numBytes); + data = indexBuffer->editData(); + auto indexPtr = reinterpret_cast(data); + for (size_t i = 0; i < numQuads; ++i) { + indexPtr[i * 6 + 0] = i * 4 + 0; + indexPtr[i * 6 + 1] = i * 4 + 1; + indexPtr[i * 6 + 2] = i * 4 + 3; + indexPtr[i * 6 + 3] = i * 4 + 1; + indexPtr[i * 6 + 4] = i * 4 + 2; + indexPtr[i * 6 + 5] = i * 4 + 3; + } + + // update transform + glm::quat rot = _transform.getRotation(); + glm::vec3 pos = _transform.getTranslation(); + Transform t; + t.setRotation(rot); + t.setTranslation(pos); + payload.setModelTransform(t); + + // transform _particleMinBound and _particleMaxBound corners into world coords + glm::vec3 d = _particleMaxBound - _particleMinBound; + glm::vec3 corners[8] = { + pos + rot * (_particleMinBound + glm::vec3(0.0f, 0.0f, 0.0f)), + pos + rot * (_particleMinBound + glm::vec3(d.x, 0.0f, 0.0f)), + pos + rot * (_particleMinBound + glm::vec3(0.0f, d.y, 0.0f)), + pos + rot * (_particleMinBound + glm::vec3(d.x, d.y, 0.0f)), + pos + rot * (_particleMinBound + glm::vec3(0.0f, 0.0f, d.z)), + pos + rot * (_particleMinBound + glm::vec3(d.x, 0.0f, d.z)), + pos + rot * (_particleMinBound + glm::vec3(0.0f, d.y, d.z)), + pos + rot * (_particleMinBound + glm::vec3(d.x, d.y, d.z)) + }; + glm::vec3 min(FLT_MAX, FLT_MAX, FLT_MAX); + glm::vec3 max = -min; + for (int i = 0; i < 8; i++) { + min.x = std::min(min.x, corners[i].x); + min.y = std::min(min.y, corners[i].y); + min.z = std::min(min.z, corners[i].z); + max.x = std::max(max.x, corners[i].x); + max.y = std::max(max.y, corners[i].y); + max.z = std::max(max.z, corners[i].z); + } + AABox bound(min, max - min); + payload.setBound(bound); + + bool textured = _texture && _texture->isLoaded(); + if (textured) { + payload.setTexture(_texture->getGPUTexture()); + payload.setPipeline(_texturedPipeline); + } else { + payload.setTexture(nullptr); + payload.setPipeline(_untexturedPipeline); + } + }); + + _scene->enqueuePendingChanges(pendingChanges); +} + +void RenderableParticleEffectEntityItem::createPipelines() { + if (!_untexturedPipeline) { + auto state = std::make_shared(); + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, + gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, + gpu::State::BLEND_OP_ADD, gpu::State::ONE); + auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(untextured_particle_vert))); + auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(untextured_particle_frag))); + auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vertShader, fragShader)); + _untexturedPipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + } + if (!_texturedPipeline) { + auto state = std::make_shared(); + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, + gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, + gpu::State::BLEND_OP_ADD, gpu::State::ONE); + auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(textured_particle_vert))); + auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(textured_particle_frag))); + auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vertShader, fragShader)); + _texturedPipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + } +} diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index 4ecea45ad0..9581c43ca5 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -16,20 +16,35 @@ #include "RenderableEntityItem.h" class RenderableParticleEffectEntityItem : public ParticleEffectEntityItem { +friend class ParticlePayload; public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); - virtual void render(RenderArgs* args); - void updateQuads(RenderArgs* args, bool textured); + virtual void update(const quint64& now) override; - SIMPLE_RENDERABLE(); + void updateRenderItem(); + + virtual bool addToScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges); + virtual void removeFromScene(EntityItemPointer self, render::ScenePointer scene, render::PendingChanges& pendingChanges); protected: + render::ItemID _renderItemId; - int _cacheID; - const int VERTS_PER_PARTICLE = 4; + struct Vertex { + Vertex(glm::vec3 xyzIn, glm::vec2 uvIn, uint32_t rgbaIn) : xyz(xyzIn), uv(uvIn), rgba(rgbaIn) {} + glm::vec3 xyz; + glm::vec2 uv; + uint32_t rgba; + }; + static void createPipelines(); + + std::vector _vertices; + static gpu::PipelinePointer _untexturedPipeline; + static gpu::PipelinePointer _texturedPipeline; + + render::ScenePointer _scene; NetworkTexturePointer _texture; }; diff --git a/libraries/entities-renderer/src/textured_particle.slf b/libraries/entities-renderer/src/textured_particle.slf new file mode 100644 index 0000000000..543aa643aa --- /dev/null +++ b/libraries/entities-renderer/src/textured_particle.slf @@ -0,0 +1,20 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// fragment shader +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +uniform sampler2D colorMap; + +varying vec4 varColor; +varying vec2 varTexCoord; + +void main(void) { + vec4 color = texture2D(colorMap, varTexCoord); + gl_FragColor = color * varColor; +} diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv new file mode 100644 index 0000000000..7564feb1ce --- /dev/null +++ b/libraries/entities-renderer/src/textured_particle.slv @@ -0,0 +1,28 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// particle vertex shader +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +varying vec4 varColor; +varying vec2 varTexCoord; + +void main(void) { + // pass along the color & uvs to fragment shader + varColor = gl_Color; + varTexCoord = gl_MultiTexCoord0.xy; + + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> +} diff --git a/libraries/entities-renderer/src/untextured_particle.slf b/libraries/entities-renderer/src/untextured_particle.slf new file mode 100644 index 0000000000..bb3ed77e3f --- /dev/null +++ b/libraries/entities-renderer/src/untextured_particle.slf @@ -0,0 +1,16 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// fragment shader +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +varying vec4 varColor; + +void main(void) { + gl_FragColor = varColor; +} diff --git a/libraries/entities-renderer/src/untextured_particle.slv b/libraries/entities-renderer/src/untextured_particle.slv new file mode 100644 index 0000000000..2975dab046 --- /dev/null +++ b/libraries/entities-renderer/src/untextured_particle.slv @@ -0,0 +1,24 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// particle vertex shader +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +varying vec4 varColor; + +void main(void) { + // pass along the diffuse color + varColor = gl_Color; + + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> +} \ No newline at end of file diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 95effa2980..a687e2be7a 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -39,6 +39,7 @@ #include "EntityTree.h" #include "EntityTreeElement.h" #include "EntitiesLogging.h" +#include "EntityScriptingInterface.h" #include "ParticleEffectEntityItem.h" const xColor ParticleEffectEntityItem::DEFAULT_COLOR = { 255, 255, 255 }; @@ -92,6 +93,74 @@ ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityIte ParticleEffectEntityItem::~ParticleEffectEntityItem() { } +void ParticleEffectEntityItem::setDimensions(const glm::vec3& value) { + computeAndUpdateDimensions(); +} + +void ParticleEffectEntityItem::setLifespan(float lifespan) { + _lifespan = lifespan; + computeAndUpdateDimensions(); +} + +void ParticleEffectEntityItem::setEmitDirection(glm::vec3 emitDirection) { + _emitDirection = glm::normalize(emitDirection); + computeAndUpdateDimensions(); +} + +void ParticleEffectEntityItem::setEmitStrength(float emitStrength) { + _emitStrength = emitStrength; + computeAndUpdateDimensions(); +} + +void ParticleEffectEntityItem::setLocalGravity(float localGravity) { + _localGravity = localGravity; + computeAndUpdateDimensions(); +} + +void ParticleEffectEntityItem::setParticleRadius(float particleRadius) { + _particleRadius = particleRadius; + computeAndUpdateDimensions(); +} + +void ParticleEffectEntityItem::computeAndUpdateDimensions() { + + const float t = _lifespan * 1.1f; // add 10% extra time, to account for incremental timer accumulation error. + const float maxOffset = (0.5f * 0.25f * _emitStrength) + _particleRadius; + + // bounds for x and z is easy to compute because there is no at^2 term. + float xMax = (_emitDirection.x * _emitStrength + maxOffset) * t; + float xMin = (_emitDirection.x * _emitStrength - maxOffset) * t; + + float zMax = (_emitDirection.z * _emitStrength + maxOffset) * t; + float zMin = (_emitDirection.z * _emitStrength - maxOffset) * t; + + // yEnd is where the particle will end. + float a = _localGravity; + float atSquared = a * t * t; + float v = _emitDirection.y * _emitStrength + maxOffset; + float vt = v * t; + float yEnd = 0.5f * atSquared + vt; + + // yApex is where the particle is at it's apex. + float yApexT = (-v / a); + float yApex = 0.0f; + + // only set apex if it's within the lifespan of the particle. + if (yApexT >= 0.0f && yApexT <= t) { + yApex = -(v * v) / (2.0f * a); + } + + float yMax = std::max(yApex, yEnd); + float yMin = std::min(yApex, yEnd); + + // times 2 because dimensions are diameters not radii. + glm::vec3 dims(2.0f * std::max(fabs(xMin), fabs(xMax)), + 2.0f * std::max(fabs(yMin), fabs(yMax)), + 2.0f * std::max(fabs(zMin), fabs(zMax))); + + EntityItem::setDimensions(dims); +} + EntityItemProperties ParticleEffectEntityItem::getProperties() const { EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class @@ -245,7 +314,7 @@ bool ParticleEffectEntityItem::isAnimatingSomething() const { } bool ParticleEffectEntityItem::needsToCallUpdate() const { - return isAnimatingSomething() ? true : EntityItem::needsToCallUpdate(); + return true; } void ParticleEffectEntityItem::update(const quint64& now) { @@ -260,13 +329,6 @@ void ParticleEffectEntityItem::update(const quint64& now) { if (isAnimatingSomething()) { stepSimulation(deltaTime); - - // update the dimensions - glm::vec3 dims; - dims.x = glm::max(glm::abs(_particleMinBound.x), glm::abs(_particleMaxBound.x)) * 2.0f; - dims.y = glm::max(glm::abs(_particleMinBound.y), glm::abs(_particleMaxBound.y)) * 2.0f; - dims.z = glm::max(glm::abs(_particleMinBound.z), glm::abs(_particleMaxBound.z)) * 2.0f; - setDimensions(dims); } EntityItem::update(now); // let our base class handle it's updates... @@ -319,7 +381,7 @@ void ParticleEffectEntityItem::setAnimationSettings(const QString& value) { qCDebug(entities) << "ParticleEffectEntityItem::setAnimationSettings() calling setAnimationFrameIndex()..."; qCDebug(entities) << " settings:" << value; qCDebug(entities) << " settingsMap[frameIndex]:" << settingsMap["frameIndex"]; - qCDebug(entities" frameIndex: %20.5f", frameIndex); + qCDebug(entities, " frameIndex: %20.5f", frameIndex); } #endif diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 3136ab6c7c..994c609f0f 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -86,12 +86,14 @@ public: void setAnimationLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } + virtual void setDimensions(const glm::vec3& value) override; + static const quint32 DEFAULT_MAX_PARTICLES; void setMaxParticles(quint32 maxParticles); quint32 getMaxParticles() const { return _maxParticles; } static const float DEFAULT_LIFESPAN; - void setLifespan(float lifespan) { _lifespan = lifespan; } + void setLifespan(float lifespan); float getLifespan() const { return _lifespan; } static const float DEFAULT_EMIT_RATE; @@ -99,21 +101,23 @@ public: float getEmitRate() const { return _emitRate; } static const glm::vec3 DEFAULT_EMIT_DIRECTION; - void setEmitDirection(glm::vec3 emitDirection) { _emitDirection = glm::normalize(emitDirection); } + void setEmitDirection(glm::vec3 emitDirection); const glm::vec3& getEmitDirection() const { return _emitDirection; } static const float DEFAULT_EMIT_STRENGTH; - void setEmitStrength(float emitStrength) { _emitStrength = emitStrength; } + void setEmitStrength(float emitStrength); float getEmitStrength() const { return _emitStrength; } static const float DEFAULT_LOCAL_GRAVITY; - void setLocalGravity(float localGravity) { _localGravity = localGravity; } + void setLocalGravity(float localGravity); float getLocalGravity() const { return _localGravity; } static const float DEFAULT_PARTICLE_RADIUS; - void setParticleRadius(float particleRadius) { _particleRadius = particleRadius; } + void setParticleRadius(float particleRadius); float getParticleRadius() const { return _particleRadius; } + void computeAndUpdateDimensions(); + bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); } float getAnimationFrameIndex() const { return _animationLoop.getFrameIndex(); } float getAnimationFPS() const { return _animationLoop.getFPS(); }