コード例 #1
0
void RenderableLightEntityItem::render(RenderArgs* args) {
    PerformanceTimer perfTimer("RenderableLightEntityItem::render");
    assert(getType() == EntityTypes::Light);
    glm::vec3 position = getPosition();
    glm::vec3 dimensions = getDimensions();
    glm::quat rotation = getRotation();
    float largestDiameter = glm::max(dimensions.x, dimensions.y, dimensions.z);

    glm::vec3 color = toGlm(getXColor());

    float intensity = getIntensity();
    float exponent = getExponent();
    float cutoff = glm::radians(getCutoff());

    if (_isSpotlight) {
        DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position, largestDiameter / 2.0f,
            color, intensity, rotation, exponent, cutoff);
    } else {
        DependencyManager::get<DeferredLightingEffect>()->addPointLight(position, largestDiameter / 2.0f,
            color, intensity);
    }
    
#ifdef WANT_DEBUG
    Q_ASSERT(args->_batch);
    gpu::Batch& batch = *args->_batch;
    batch.setModelTransform(getTransformToCenter());
    DependencyManager::get<GeometryCache>()->renderWireSphere(batch, 0.5f, 15, 15, glm::vec4(color, 1.0f));
#endif
};
コード例 #2
0
void RenderableBoxEntityItem::render(RenderArgs* args) {
    PerformanceTimer perfTimer("RenderableBoxEntityItem::render");
    Q_ASSERT(getType() == EntityTypes::Box);
    Q_ASSERT(args->_batch);

    if (!_procedural) {
        _procedural.reset(new Procedural(this->getUserData()));
        _procedural->_vertexSource = simple_vert;
        _procedural->_fragmentSource = simple_frag;
        _procedural->_state->setCullMode(gpu::State::CULL_NONE);
        _procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL);
        _procedural->_state->setBlendFunction(false,
            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);
    }

    gpu::Batch& batch = *args->_batch;
    glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha());

    if (_procedural->ready()) {
        batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well
        _procedural->prepare(batch, this->getDimensions());
        auto color = _procedural->getColor(cubeColor);
        batch._glColor4f(color.r, color.g, color.b, color.a);
        DependencyManager::get<GeometryCache>()->renderCube(batch);
    } else {
        DependencyManager::get<DeferredLightingEffect>()->renderSolidCubeInstance(batch, getTransformToCenter(), cubeColor);
    }

    RenderableDebugableEntityItem::render(this, args);
};
コード例 #3
0
ファイル: SphereEntityItem.cpp プロジェクト: Schackasawa/hifi
EntityItemProperties SphereEntityItem::getProperties() const {
    EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class

    properties.setColor(getXColor());
    properties.setGlowLevel(getGlowLevel());

    return properties;
}
コード例 #4
0
void RenderablePolyLineEntityItem::update(const quint64& now) {
    PolyLineUniforms uniforms;
    uniforms.color = toGlm(getXColor());
    memcpy(&_uniformBuffer.edit<PolyLineUniforms>(), &uniforms, sizeof(PolyLineUniforms));
    if (_pointsChanged || _strokeWidthsChanged || _normalsChanged) {
        updateVertices();
        updateGeometry();
    }

}
コード例 #5
0
void RenderableShapeEntityItem::render(RenderArgs* args) {
    PerformanceTimer perfTimer("RenderableShapeEntityItem::render");
    //Q_ASSERT(getType() == EntityTypes::Shape);
    Q_ASSERT(args->_batch);
    checkFading();

    if (!_procedural) {
        _procedural.reset(new Procedural(getUserData()));
        _procedural->_vertexSource = simple_vert;
        _procedural->_fragmentSource = simple_frag;
        _procedural->_opaqueState->setCullMode(gpu::State::CULL_NONE);
        _procedural->_opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL);
        PrepareStencil::testMaskDrawShape(*_procedural->_opaqueState);
        _procedural->_opaqueState->setBlendFunction(false,
            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);
    }

    gpu::Batch& batch = *args->_batch;
    glm::vec4 color(toGlm(getXColor()), getLocalRenderAlpha());
    bool success;
    Transform modelTransform = getTransformToCenter(success);
    if (!success) {
        return;
    }
    if (_shape == entity::Sphere) {
        modelTransform.postScale(SPHERE_ENTITY_SCALE);
    }
    batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation
    if (_procedural->ready()) {
        _procedural->prepare(batch, getPosition(), getDimensions(), getOrientation());
        auto outColor = _procedural->getColor(color);
        outColor.a *= _procedural->isFading() ? Interpolate::calculateFadeRatio(_procedural->getFadeStartTime()) : 1.0f;
        batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a);
        if (render::ShapeKey(args->_globalShapeKey).isWireframe()) {
            DependencyManager::get<GeometryCache>()->renderWireShape(batch, MAPPING[_shape]);
        } else {
            DependencyManager::get<GeometryCache>()->renderShape(batch, MAPPING[_shape]);
        }
    } else {
        // FIXME, support instanced multi-shape rendering using multidraw indirect
        color.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
        auto geometryCache = DependencyManager::get<GeometryCache>();
        auto pipeline = color.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
        
        if (render::ShapeKey(args->_globalShapeKey).isWireframe()) {
            geometryCache->renderWireShapeInstance(args, batch, MAPPING[_shape], color, pipeline);
        } else {
            geometryCache->renderSolidShapeInstance(args, batch, MAPPING[_shape], color, pipeline);
        }
    }

    static const auto triCount = DependencyManager::get<GeometryCache>()->getShapeTriangleCount(MAPPING[_shape]);
    args->_details._trianglesRendered += (int)triCount;
}
コード例 #6
0
EntityItemProperties BoxEntityItem::getProperties() const {
    EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class

    properties._color = getXColor();
    properties._colorChanged = false;

    properties._glowLevel = getGlowLevel();
    properties._glowLevelChanged = false;

    return properties;
}
コード例 #7
0
void RenderableLineEntityItem::updateGeometry() {
    auto geometryCache = DependencyManager::get<GeometryCache>();
    if (_lineVerticesID == GeometryCache::UNKNOWN_ID) {
        _lineVerticesID = geometryCache ->allocateID();
    }
    if (_pointsChanged) {
        glm::vec4 lineColor(toGlm(getXColor()), getLocalRenderAlpha());
        geometryCache->updateVertices(_lineVerticesID, getLinePoints(), lineColor);
        _pointsChanged = false;
    }
}
コード例 #8
0
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);
    }
}
コード例 #9
0
ファイル: LineEntityItem.cpp プロジェクト: kencooke/hifi
EntityItemProperties LineEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
    
    EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class

    
    properties._color = getXColor();
    properties._colorChanged = false;
    
    
    COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineWidth, getLineWidth);
    
    COPY_ENTITY_PROPERTY_TO_PROPERTIES(linePoints, getLinePoints);

    return properties;
}
コード例 #10
0
void RenderableLineEntityItem::render(RenderArgs* args) {
    PerformanceTimer perfTimer("RenderableLineEntityItem::render");
    assert(getType() == EntityTypes::Line);
    glm::vec3 position = getPosition();
    glm::vec3 dimensions = getDimensions();
    glm::quat rotation = getRotation();
    glm::vec4 lineColor(toGlm(getXColor()), getLocalRenderAlpha());
    glPushMatrix();
        glTranslatef(position.x, position.y, position.z);
        glm::vec3 axis = glm::axis(rotation);
        glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
        glm::vec3 p1 = {0.0f, 0.0f, 0.0f};
        glm::vec3& p2 = dimensions;
        DependencyManager::get<DeferredLightingEffect>()->renderLine(p1, p2, lineColor, lineColor);
    glPopMatrix();
    RenderableDebugableEntityItem::render(this, args);
};
コード例 #11
0
ファイル: PolyLineEntityItem.cpp プロジェクト: ZappoMan/hifi
EntityItemProperties PolyLineEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
    QWriteLocker lock(&_quadReadWriteLock);
    EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class


    properties._color = getXColor();
    properties._colorChanged = false;
    

    COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineWidth, getLineWidth);
    COPY_ENTITY_PROPERTY_TO_PROPERTIES(linePoints, getLinePoints);
    COPY_ENTITY_PROPERTY_TO_PROPERTIES(normals, getNormals);
    COPY_ENTITY_PROPERTY_TO_PROPERTIES(strokeColors, getStrokeColors);
    COPY_ENTITY_PROPERTY_TO_PROPERTIES(strokeWidths, getStrokeWidths);
    COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures);
    COPY_ENTITY_PROPERTY_TO_PROPERTIES(isUVModeStretch, getIsUVModeStretch);
    return properties;
}
コード例 #12
0
void RenderableBoxEntityItem::render(RenderArgs* args) {
    PerformanceTimer perfTimer("RenderableBoxEntityItem::render");
    Q_ASSERT(getType() == EntityTypes::Box);
    Q_ASSERT(args->_batch);
    gpu::Batch& batch = *args->_batch;
    batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well
    
    if (!_procedural) {
        _procedural.reset(new ProceduralInfo(this));
    }

    if (_procedural->ready()) {
        _procedural->prepare(batch);
        DependencyManager::get<GeometryCache>()->renderUnitCube(batch);
    } else {
        glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha());
        DependencyManager::get<DeferredLightingEffect>()->renderSolidCube(batch, 1.0f, cubeColor);
    }

    RenderableDebugableEntityItem::render(this, args);
};
コード例 #13
0
void RenderableLightEntityItem::updateRenderItemFromEntity(LightPayload& lightPayload) {
    auto entity = this;

    lightPayload.setVisible(entity->getVisible());

    auto light = lightPayload.editLight();
    light->setPosition(entity->getPosition());
    light->setOrientation(entity->getRotation());

    bool success;
    lightPayload.editBound() = entity->getAABox(success);
    if (!success) {
        lightPayload.editBound() = render::Item::Bound();
    }

    glm::vec3 dimensions = entity->getDimensions();
    float largestDiameter = glm::compMax(dimensions);
    light->setMaximumRadius(largestDiameter / 2.0f);

    light->setColor(toGlm(entity->getXColor()));

    float intensity = entity->getIntensity();//* entity->getFadingRatio();
    light->setIntensity(intensity);

    light->setFalloffRadius(entity->getFalloffRadius());


    float exponent = entity->getExponent();
    float cutoff = glm::radians(entity->getCutoff());
    if (!entity->getIsSpotlight()) {
        light->setType(model::Light::POINT);
    } else {
        light->setType(model::Light::SPOT);

        light->setSpotAngle(cutoff);
        light->setSpotExponent(exponent);
    }

}
コード例 #14
0
void RenderableBoxEntityItem::render(RenderArgs* args) {
    PerformanceTimer perfTimer("RenderableBoxEntityItem::render");
    Q_ASSERT(getType() == EntityTypes::Box);
    Q_ASSERT(args->_batch);


    if (!_procedural) {
        _procedural.reset(new Procedural(this->getUserData()));
        _procedural->_vertexSource = simple_vert;
        _procedural->_fragmentSource = simple_frag;
        _procedural->_state->setCullMode(gpu::State::CULL_NONE);
        _procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL);
        _procedural->_state->setBlendFunction(false,
            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);
    }

    gpu::Batch& batch = *args->_batch;
    glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha());

    bool success;
    auto transToCenter = getTransformToCenter(success);
    if (!success) {
        return;
    }

    batch.setModelTransform(transToCenter); // we want to include the scale as well
    if (_procedural->ready()) {
        _procedural->prepare(batch, getPosition(), getDimensions());
        auto color = _procedural->getColor(cubeColor);
        batch._glColor4f(color.r, color.g, color.b, color.a);
        DependencyManager::get<GeometryCache>()->renderCube(batch);
    } else {
        DependencyManager::get<GeometryCache>()->renderSolidCubeInstance(batch, cubeColor);
    }
    static const auto triCount = DependencyManager::get<GeometryCache>()->getCubeTriangleCount();
    args->_details._trianglesRendered += (int)triCount;
}
コード例 #15
0
ファイル: ShapeEntityItem.cpp プロジェクト: BingShearer/hifi
EntityItemProperties ShapeEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
    EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
    properties.setColor(getXColor());
    properties.setShape(entity::stringFromShape(getShape()));
    return properties;
}
コード例 #16
0
void ParticleEffectEntityItem::stepSimulation(float deltaTime) {

    _particleMinBound = glm::vec3(-1.0f, -1.0f, -1.0f);
    _particleMaxBound = glm::vec3(1.0f, 1.0f, 1.0f);

    // update particles between head and tail
    for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) {
        _particleLifetimes[i] -= deltaTime;

        // if particle has died.
        if (_particleLifetimes[i] <= 0.0f || _lifespan == 0.0f) {
            // move head forward
            _particleHeadIndex = (_particleHeadIndex + 1) % _maxParticles;
        }
        else {
            float age = 1.0f - _particleLifetimes[i] / _lifespan;  // 0.0 .. 1.0
            updateRadius(i, age);
            updateColor(i, age);
            updateAlpha(i, age);
            integrateParticle(i, deltaTime);
            extendBounds(_particlePositions[i]);
        }
    }

    // emit new particles, but only if we are emmitting
    if (getIsEmitting() && _emitRate > 0.0f && _lifespan > 0.0f && _polarStart <= _polarFinish) {

        float timeLeftInFrame = deltaTime;
        while (_timeUntilNextEmit < timeLeftInFrame) {

            timeLeftInFrame -= _timeUntilNextEmit;
            _timeUntilNextEmit = 1.0f / _emitRate;

            // emit a new particle at tail index.
            quint32 i = _particleTailIndex;
            _particleLifetimes[i] = _lifespan;

            // Radius
            if (_radiusSpread == 0.0f) {
                _radiusStarts[i] = getRadiusStart();
                _radiusMiddles[i] =_particleRadius;
                _radiusFinishes[i] = getRadiusFinish();
            } else {
                float spreadMultiplier;
                if (_particleRadius > 0.0f) {
                    spreadMultiplier = 1.0f + randFloatInRange(-1.0f, 1.0f) * _radiusSpread / _particleRadius;
                } else {
                    spreadMultiplier = 1.0f;
                }
                _radiusStarts[i] = 
                    glm::clamp(spreadMultiplier * getRadiusStart(), MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS);
                _radiusMiddles[i] = 
                    glm::clamp(spreadMultiplier * _particleRadius, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS);
                _radiusFinishes[i] = 
                    glm::clamp(spreadMultiplier * getRadiusFinish(), MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS);
            }
            updateRadius(i, 0.0f);

            // Position, velocity, and acceleration
            if (_polarStart == 0.0f && _polarFinish == 0.0f && _emitDimensions.z == 0.0f) {
                // Emit along z-axis from position
                _particlePositions[i] = getPosition();
                _particleVelocities[i] = 
                    (_emitSpeed + randFloatInRange(-1.0f, 1.0f) * _speedSpread) * (_emitOrientation * Z_AXIS);
                _particleAccelerations[i] = _emitAcceleration + randFloatInRange(-1.0f, 1.0f) * _accelerationSpread;

            } else {
                // Emit around point or from ellipsoid
                // - Distribute directions evenly around point
                // - Distribute points relatively evenly over ellipsoid surface
                // - Distribute points relatively evenly within ellipsoid volume

                float elevationMinZ = sin(PI_OVER_TWO - _polarFinish);
                float elevationMaxZ = sin(PI_OVER_TWO - _polarStart);
                float elevation = asin(elevationMinZ + (elevationMaxZ - elevationMinZ) * randFloat());

                float azimuth;
                if (_azimuthFinish >= _azimuthStart) {
                    azimuth = _azimuthStart + (_azimuthFinish - _azimuthStart) * randFloat();
                } else {
                    azimuth = _azimuthStart + (TWO_PI + _azimuthFinish - _azimuthStart) * randFloat();
                }

                glm::vec3 emitDirection;

                if (_emitDimensions == glm::vec3()) {
                    // Point
                    emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Z_AXIS;

                    _particlePositions[i] = getPosition();
                } else {
                    // Ellipsoid
                    float radiusScale = 1.0f;
                    if (_emitRadiusStart < 1.0f) {
                        float emitRadiusStart = glm::max(_emitRadiusStart, EPSILON);  // Avoid math complications at center
                        float randRadius = 
                            emitRadiusStart + randFloatInRange(0.0f, MAXIMUM_EMIT_RADIUS_START - emitRadiusStart);
                        radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f);
                    }

                    glm::vec3 radiuses = radiusScale * 0.5f * _emitDimensions;
                    float x = radiuses.x * glm::cos(elevation) * glm::cos(azimuth);
                    float y = radiuses.y * glm::cos(elevation) * glm::sin(azimuth);
                    float z = radiuses.z * glm::sin(elevation);
                    glm::vec3 emitPosition = glm::vec3(x, y, z);
                    emitDirection = glm::normalize(glm::vec3(
                        radiuses.x > 0.0f ? x / (radiuses.x * radiuses.x) : 0.0f,
                        radiuses.y > 0.0f ? y / (radiuses.y * radiuses.y) : 0.0f,
                        radiuses.z > 0.0f ? z / (radiuses.z * radiuses.z) : 0.0f
                        ));

                    _particlePositions[i] = getPosition() + _emitOrientation * emitPosition;
                }

                _particleVelocities[i] =
                    (_emitSpeed + randFloatInRange(-1.0f, 1.0f) * _speedSpread) * (_emitOrientation * emitDirection);
                _particleAccelerations[i] = _emitAcceleration + randFloatInRange(-1.0f, 1.0f) * _accelerationSpread;
            }
            integrateParticle(i, timeLeftInFrame);
            extendBounds(_particlePositions[i]);

            // Color
            if (_colorSpread == xColor{ 0, 0, 0 }) {
                _colorStarts[i] = getColorStart();
                _colorMiddles[i] = getXColor();
                _colorFinishes[i] = getColorFinish();
            } else {
                xColor startColor = getColorStart();
                xColor middleColor = getXColor();
                xColor finishColor = getColorFinish();

                float spread = randFloatInRange(-1.0f, 1.0f);
                float spreadMultiplierRed = 
                    middleColor.red > 0 ? 1.0f + spread * (float)_colorSpread.red / (float)middleColor.red : 1.0f;
                float spreadMultiplierGreen = 
                    middleColor.green > 0 ? 1.0f + spread * (float)_colorSpread.green / (float)middleColor.green : 1.0f;
                float spreadMultiplierBlue = 
                    middleColor.blue > 0 ? 1.0f + spread * (float)_colorSpread.blue / (float)middleColor.blue : 1.0f;

                _colorStarts[i].red = (int)glm::clamp(spreadMultiplierRed * (float)startColor.red, 0.0f, 255.0f);
                _colorStarts[i].green = (int)glm::clamp(spreadMultiplierGreen * (float)startColor.green, 0.0f, 255.0f);
                _colorStarts[i].blue = (int)glm::clamp(spreadMultiplierBlue * (float)startColor.blue, 0.0f, 255.0f);

                _colorMiddles[i].red = (int)glm::clamp(spreadMultiplierRed * (float)middleColor.red, 0.0f, 255.0f);
                _colorMiddles[i].green = (int)glm::clamp(spreadMultiplierGreen * (float)middleColor.green, 0.0f, 255.0f);
                _colorMiddles[i].blue = (int)glm::clamp(spreadMultiplierBlue * (float)middleColor.blue, 0.0f, 255.0f);

                _colorFinishes[i].red = (int)glm::clamp(spreadMultiplierRed * (float)finishColor.red, 0.0f, 255.0f);
                _colorFinishes[i].green = (int)glm::clamp(spreadMultiplierGreen * (float)finishColor.green, 0.0f, 255.0f);
                _colorFinishes[i].blue = (int)glm::clamp(spreadMultiplierBlue * (float)finishColor.blue, 0.0f, 255.0f);
            }
            updateColor(i, 0.0f);

            // Alpha
            if (_alphaSpread == 0.0f) {
                _alphaStarts[i] = getAlphaStart();
                _alphaMiddles[i] = _alpha;
                _alphaFinishes[i] = getAlphaFinish();
            } else {
                float spreadMultiplier = 1.0f + randFloatInRange(-1.0f, 1.0f) * _alphaSpread / _alpha;
                _alphaStarts[i] = spreadMultiplier * getAlphaStart();
                _alphaMiddles[i] = spreadMultiplier * _alpha;
                _alphaFinishes[i] = spreadMultiplier * getAlphaFinish();
            }
            updateAlpha(i, 0.0f);

            _particleTailIndex = (_particleTailIndex + 1) % _maxParticles;

            // overflow! move head forward by one.
            // because the case of head == tail indicates an empty array, not a full one.
            // This can drop an existing older particle, but this is by design, newer particles are a higher priority.
            if (_particleTailIndex == _particleHeadIndex) {
                _particleHeadIndex = (_particleHeadIndex + 1) % _maxParticles;
            }
        }

        _timeUntilNextEmit -= timeLeftInFrame;
    }
}