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