Пример #1
0
void ParticleSystem::Node::update(const std::vector<Particle>& set) {
  if (m_geometry.vertexDataSize() < 4 * set.size()) {
    m_geometry.allocate(set.size() * 4 * 2, set.size() * 6 * 2 - 2);
    generateTriangleStrip(m_geometry.indexData<GLuint>(),
                          m_geometry.indexDataSize());
  }

  Vertex* array = m_geometry.vertexData<Vertex>();

  int it = 0;
  for (const Particle& p : set) {
    array[4 * it + 0] = Vertex(QPointF(p.x - p.r, p.y - p.r), QPointF(0, 0), p);
    array[4 * it + 1] = Vertex(QPointF(p.x + p.r, p.y - p.r), QPointF(1, 0), p);
    array[4 * it + 2] = Vertex(QPointF(p.x - p.r, p.y + p.r), QPointF(0, 1), p);
    array[4 * it + 3] = Vertex(QPointF(p.x + p.r, p.y + p.r), QPointF(1, 1), p);
    it++;
  }

  m_geometry.setVertexCount(set.size() * 4);
  m_geometry.setIndexCount(set.size() * 6 - 2);
  m_geometry.updateVertexData();
}
/**
 * Generate the shadow spot light of shape lightPoly and a object poly
 *
 * @param isCasterOpaque whether the caster is opaque
 * @param lightCenter the center of the light
 * @param lightSize the radius of the light
 * @param poly x,y,z vertexes of a convex polygon that occludes the light source
 * @param polyLength number of vertexes of the occluding polygon
 * @param shadowTriangleStrip return an (x,y,alpha) triangle strip representing the shadow. Return
 *                            empty strip if error.
 */
void SpotShadow::createSpotShadow(bool isCasterOpaque, const Vector3& lightCenter,
        float lightSize, const Vector3* poly, int polyLength, const Vector3& polyCentroid,
        VertexBuffer& shadowTriangleStrip) {
    if (CC_UNLIKELY(lightCenter.z <= 0)) {
        ALOGW("Relative Light Z is not positive. No spot shadow!");
        return;
    }
    if (CC_UNLIKELY(polyLength < 3)) {
#if DEBUG_SHADOW
        ALOGW("Invalid polygon length. No spot shadow!");
#endif
        return;
    }
    OutlineData outlineData[polyLength];
    Vector2 outlineCentroid;
    // Calculate the projected outline for each polygon's vertices from the light center.
    //
    //                       O     Light
    //                      /
    //                    /
    //                   .     Polygon vertex
    //                 /
    //               /
    //              O     Outline vertices
    //
    // Ratio = (Poly - Outline) / (Light - Poly)
    // Outline.x = Poly.x - Ratio * (Light.x - Poly.x)
    // Outline's radius / Light's radius = Ratio

    // Compute the last outline vertex to make sure we can get the normal and outline
    // in one single loop.
    projectCasterToOutline(outlineData[polyLength - 1].position, lightCenter,
            poly[polyLength - 1]);

    // Take the outline's polygon, calculate the normal for each outline edge.
    int currentNormalIndex = polyLength - 1;
    int nextNormalIndex = 0;

    for (int i = 0; i < polyLength; i++) {
        float ratioZ = projectCasterToOutline(outlineData[i].position,
                lightCenter, poly[i]);
        outlineData[i].radius = ratioZ * lightSize;

        outlineData[currentNormalIndex].normal = ShadowTessellator::calculateNormal(
                outlineData[currentNormalIndex].position,
                outlineData[nextNormalIndex].position);
        currentNormalIndex = (currentNormalIndex + 1) % polyLength;
        nextNormalIndex++;
    }

    projectCasterToOutline(outlineCentroid, lightCenter, polyCentroid);

    int penumbraIndex = 0;
    // Then each polygon's vertex produce at minmal 2 penumbra vertices.
    // Since the size can be dynamic here, we keep track of the size and update
    // the real size at the end.
    int allocatedPenumbraLength = 2 * polyLength + SPOT_MAX_EXTRA_CORNER_VERTEX_NUMBER;
    Vector2 penumbra[allocatedPenumbraLength];
    int totalExtraCornerSliceNumber = 0;

    Vector2 umbra[polyLength];

    // When centroid is covered by all circles from outline, then we consider
    // the umbra is invalid, and we will tune down the shadow strength.
    bool hasValidUmbra = true;
    // We need the minimal of RaitoVI to decrease the spot shadow strength accordingly.
    float minRaitoVI = FLT_MAX;

    for (int i = 0; i < polyLength; i++) {
        // Generate all the penumbra's vertices only using the (outline vertex + normal * radius)
        // There is no guarantee that the penumbra is still convex, but for
        // each outline vertex, it will connect to all its corresponding penumbra vertices as
        // triangle fans. And for neighber penumbra vertex, it will be a trapezoid.
        //
        // Penumbra Vertices marked as Pi
        // Outline Vertices marked as Vi
        //                                            (P3)
        //          (P2)                               |     ' (P4)
        //   (P1)'   |                                 |   '
        //         ' |                                 | '
        // (P0)  ------------------------------------------------(P5)
        //           | (V0)                            |(V1)
        //           |                                 |
        //           |                                 |
        //           |                                 |
        //           |                                 |
        //           |                                 |
        //           |                                 |
        //           |                                 |
        //           |                                 |
        //       (V3)-----------------------------------(V2)
        int preNormalIndex = (i + polyLength - 1) % polyLength;

        const Vector2& previousNormal = outlineData[preNormalIndex].normal;
        const Vector2& currentNormal = outlineData[i].normal;

        // Depending on how roundness we want for each corner, we can subdivide
        // further here and/or introduce some heuristic to decide how much the
        // subdivision should be.
        int currentExtraSliceNumber = ShadowTessellator::getExtraVertexNumber(
                previousNormal, currentNormal, SPOT_CORNER_RADIANS_DIVISOR);

        int currentCornerSliceNumber = 1 + currentExtraSliceNumber;
        totalExtraCornerSliceNumber += currentExtraSliceNumber;
#if DEBUG_SHADOW
        ALOGD("currentExtraSliceNumber should be %d", currentExtraSliceNumber);
        ALOGD("currentCornerSliceNumber should be %d", currentCornerSliceNumber);
        ALOGD("totalCornerSliceNumber is %d", totalExtraCornerSliceNumber);
#endif
        if (CC_UNLIKELY(totalExtraCornerSliceNumber > SPOT_MAX_EXTRA_CORNER_VERTEX_NUMBER)) {
            currentCornerSliceNumber = 1;
        }
        for (int k = 0; k <= currentCornerSliceNumber; k++) {
            Vector2 avgNormal =
                    (previousNormal * (currentCornerSliceNumber - k) + currentNormal * k) /
                    currentCornerSliceNumber;
            avgNormal.normalize();
            penumbra[penumbraIndex++] = outlineData[i].position +
                    avgNormal * outlineData[i].radius;
        }


        // Compute the umbra by the intersection from the outline's centroid!
        //
        //       (V) ------------------------------------
        //           |          '                       |
        //           |         '                        |
        //           |       ' (I)                      |
        //           |    '                             |
        //           | '             (C)                |
        //           |                                  |
        //           |                                  |
        //           |                                  |
        //           |                                  |
        //           ------------------------------------
        //
        // Connect a line b/t the outline vertex (V) and the centroid (C), it will
        // intersect with the outline vertex's circle at point (I).
        // Now, ratioVI = VI / VC, ratioIC = IC / VC
        // Then the intersetion point can be computed as Ixy = Vxy * ratioIC + Cxy * ratioVI;
        //
        // When all of the outline circles cover the the outline centroid, (like I is
        // on the other side of C), there is no real umbra any more, so we just fake
        // a small area around the centroid as the umbra, and tune down the spot
        // shadow's umbra strength to simulate the effect the whole shadow will
        // become lighter in this case.
        // The ratio can be simulated by using the inverse of maximum of ratioVI for
        // all (V).
        float distOutline = (outlineData[i].position - outlineCentroid).length();
        if (CC_UNLIKELY(distOutline == 0)) {
            // If the outline has 0 area, then there is no spot shadow anyway.
            ALOGW("Outline has 0 area, no spot shadow!");
            return;
        }

        float ratioVI = outlineData[i].radius / distOutline;
        minRaitoVI = MathUtils::min(minRaitoVI, ratioVI);
        if (ratioVI >= (1 - FAKE_UMBRA_SIZE_RATIO)) {
            ratioVI = (1 - FAKE_UMBRA_SIZE_RATIO);
        }
        // When we know we don't have valid umbra, don't bother to compute the
        // values below. But we can't skip the loop yet since we want to know the
        // maximum ratio.
        float ratioIC = 1 - ratioVI;
        umbra[i] = outlineData[i].position * ratioIC + outlineCentroid * ratioVI;
    }

    hasValidUmbra = (minRaitoVI <= 1.0);
    float shadowStrengthScale = 1.0;
    if (!hasValidUmbra) {
#if DEBUG_SHADOW
        ALOGW("The object is too close to the light or too small, no real umbra!");
#endif
        for (int i = 0; i < polyLength; i++) {
            umbra[i] = outlineData[i].position * FAKE_UMBRA_SIZE_RATIO +
                    outlineCentroid * (1 - FAKE_UMBRA_SIZE_RATIO);
        }
        shadowStrengthScale = 1.0 / minRaitoVI;
    }

    int penumbraLength = penumbraIndex;
    int umbraLength = polyLength;

#if DEBUG_SHADOW
    ALOGD("penumbraLength is %d , allocatedPenumbraLength %d", penumbraLength, allocatedPenumbraLength);
    dumpPolygon(poly, polyLength, "input poly");
    dumpPolygon(penumbra, penumbraLength, "penumbra");
    dumpPolygon(umbra, umbraLength, "umbra");
    ALOGD("hasValidUmbra is %d and shadowStrengthScale is %f", hasValidUmbra, shadowStrengthScale);
#endif

    // The penumbra and umbra needs to be in convex shape to keep consistency
    // and quality.
    // Since we are still shooting rays to penumbra, it needs to be convex.
    // Umbra can be represented as a fan from the centroid, but visually umbra
    // looks nicer when it is convex.
    Vector2 finalUmbra[umbraLength];
    Vector2 finalPenumbra[penumbraLength];
    int finalUmbraLength = hull(umbra, umbraLength, finalUmbra);
    int finalPenumbraLength = hull(penumbra, penumbraLength, finalPenumbra);

    generateTriangleStrip(isCasterOpaque, shadowStrengthScale, finalPenumbra,
            finalPenumbraLength, finalUmbra, finalUmbraLength, poly, polyLength,
            shadowTriangleStrip, outlineCentroid);

}