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<glm::vec3> vertices; QVector<glm::vec3> positions; QVector<glm::vec2> 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<GeometryCache>()->updateVertices(_cacheID, vertices, textureCoords, particleColor); } else { DependencyManager::get<GeometryCache>()->updateVertices(_cacheID, vertices, particleColor); } }
bool ParticleEffectEntityItem::isAnimatingSomething() const { // keep animating if there are particles still alive. return (getAnimationIsPlaying() || getLivingParticleCount() > 0) && getAnimationFPS() != 0.0f; }
void RenderableParticleEffectEntityItem::updateRenderItem() { if (!_scene) { return; } // make a copy of each particle's details std::vector<ParticleDetails> particleDetails; particleDetails.reserve(getLivingParticleCount()); for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { auto xcolor = _particleColors[i]; auto alpha = (uint8_t)(glm::clamp(_particleAlphas[i] * getLocalRenderAlpha(), 0.0f, 1.0f) * 255.0f); auto rgba = toRGBA(xcolor.red, xcolor.green, xcolor.blue, alpha); particleDetails.push_back(ParticleDetails(_particlePositions[i], _particleRadiuses[i], rgba)); } // 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(particleDetails.begin(), particleDetails.end(), zSort); // allocate vertices _vertices.clear(); // build vertices from particle positions and radiuses glm::vec3 frustumPosition = frustum->getPosition(); for (auto&& particle : particleDetails) { glm::vec3 particleDirection = particle.position - frustumPosition; glm::vec3 right = glm::normalize(glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), particleDirection)); glm::vec3 up = glm::normalize(glm::cross(right, particleDirection)); glm::vec3 upOffset = up * particle.radius; glm::vec3 rightOffset = right * particle.radius; // generate corners of quad aligned to face the camera. _vertices.emplace_back(particle.position + rightOffset + upOffset, glm::vec2(1.0f, 1.0f), particle.rgba); _vertices.emplace_back(particle.position - rightOffset + upOffset, glm::vec2(0.0f, 1.0f), particle.rgba); _vertices.emplace_back(particle.position - rightOffset - upOffset, glm::vec2(0.0f, 0.0f), particle.rgba); _vertices.emplace_back(particle.position + rightOffset - upOffset, glm::vec2(1.0f, 0.0f), particle.rgba); } render::PendingChanges pendingChanges; pendingChanges.updateItem<ParticlePayload>(_renderItemId, [this](ParticlePayload& payload) { // update vertex buffer auto vertexBuffer = payload.getVertexBuffer(); size_t numBytes = sizeof(Vertex) * _vertices.size(); if (numBytes == 0) { vertexBuffer->resize(0); auto indexBuffer = payload.getIndexBuffer(); indexBuffer->resize(0); return; } 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(); const size_t NUM_VERTS_PER_PARTICLE = 4; const size_t NUM_INDICES_PER_PARTICLE = 6; auto numQuads = (_vertices.size() / NUM_VERTS_PER_PARTICLE); numBytes = sizeof(uint16_t) * numQuads * NUM_INDICES_PER_PARTICLE; indexBuffer->resize(numBytes); data = indexBuffer->editData(); auto indexPtr = reinterpret_cast<uint16_t*>(data); for (size_t i = 0; i < numQuads; ++i) { indexPtr[i * NUM_INDICES_PER_PARTICLE + 0] = i * NUM_VERTS_PER_PARTICLE + 0; indexPtr[i * NUM_INDICES_PER_PARTICLE + 1] = i * NUM_VERTS_PER_PARTICLE + 1; indexPtr[i * NUM_INDICES_PER_PARTICLE + 2] = i * NUM_VERTS_PER_PARTICLE + 3; indexPtr[i * NUM_INDICES_PER_PARTICLE + 3] = i * NUM_VERTS_PER_PARTICLE + 1; indexPtr[i * NUM_INDICES_PER_PARTICLE + 4] = i * NUM_VERTS_PER_PARTICLE + 2; indexPtr[i * NUM_INDICES_PER_PARTICLE + 5] = i * NUM_VERTS_PER_PARTICLE + 3; } // update transform glm::quat rot = _transform.getRotation(); glm::vec3 pos = _transform.getTranslation(); Transform t; t.setRotation(rot); payload.setModelTransform(t); // transform _particleMinBound and _particleMaxBound corners into world coords glm::vec3 d = _particleMaxBound - _particleMinBound; const size_t NUM_BOX_CORNERS = 8; glm::vec3 corners[NUM_BOX_CORNERS] = { 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 (size_t i = 0; i < NUM_BOX_CORNERS; 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); }
bool ParticleEffectEntityItem::isEmittingParticles() const { // keep emitting if there are particles still alive. return (getIsEmitting() || getLivingParticleCount() > 0); }