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