From 726118b35240957710d4d85fb5747e2ba8b934f7 Mon Sep 17 00:00:00 2001 From: Chris Craik Date: Fri, 7 Mar 2014 18:27:49 -0800 Subject: [PATCH] Improve shadow tessellation performance - Tune and simplify shadow parameters - Remove additional inner rings - Improve polygon ray casting algorithm Change-Id: If0f28b2d66ae0480b675942bb65e8fcd2864425d --- libs/hwui/AmbientShadow.cpp | 54 +++++---- libs/hwui/Caches.cpp | 4 +- libs/hwui/DisplayList.cpp | 2 +- libs/hwui/ShadowTessellator.cpp | 29 +++-- libs/hwui/ShadowTessellator.h | 10 +- libs/hwui/SpotShadow.cpp | 247 +++++++++++++++++++++------------------- libs/hwui/SpotShadow.h | 6 +- 7 files changed, 175 insertions(+), 177 deletions(-) diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp index 4935b342de28..8327ef72e978 100644 --- a/libs/hwui/AmbientShadow.cpp +++ b/libs/hwui/AmbientShadow.cpp @@ -47,9 +47,8 @@ void AmbientShadow::createAmbientShadow(const Vector3* vertices, int vertexCount const Vector3& centroid3d, float heightFactor, float geomFactor, VertexBuffer& shadowVertexBuffer) { const int rays = SHADOW_RAY_COUNT; - const int layers = SHADOW_LAYER_COUNT; // Validate the inputs. - if (vertexCount < 3 || heightFactor <= 0 || layers <= 0 || rays <= 0 + if (vertexCount < 3 || heightFactor <= 0 || rays <= 0 || geomFactor <= 0) { #if DEBUG_SHADOW ALOGE("Invalid input for createAmbientShadow(), early return!"); @@ -96,33 +95,32 @@ void AmbientShadow::createAmbientShadow(const Vector3* vertices, int vertexCount // calculate the normal N, which should be perpendicular to the edge of the // polygon (represented by the neighbor intersection points) . // Shadow's vertices will be generated as : P + N * scale. - int currentVertexIndex = 0; - for (int layerIndex = 0; layerIndex <= layers; layerIndex++) { - for (int rayIndex = 0; rayIndex < rays; rayIndex++) { - - Vector2 normal(1.0f, 0.0f); - calculateNormal(rays, rayIndex, dir.array(), rayDist, normal); - - float opacity = 1.0 / (1 + rayHeight[rayIndex] / heightFactor); - - // The vertex should be start from rayDist[i] then scale the - // normalizeNormal! - Vector2 intersection = dir[rayIndex] * rayDist[rayIndex] + - Vector2(centroid3d.x, centroid3d.y); - - float layerRatio = layerIndex / (float)(layers); - // The higher the intersection is, the further the ambient shadow expanded. - float expansionDist = rayHeight[rayIndex] / heightFactor * - geomFactor * (1 - layerRatio); - AlphaVertex::set(&shadowVertices[currentVertexIndex++], - intersection.x + normal.x * expansionDist, - intersection.y + normal.y * expansionDist, - layerRatio * opacity); - } - + for (int rayIndex = 0; rayIndex < rays; rayIndex++) { + Vector2 normal(1.0f, 0.0f); + calculateNormal(rays, rayIndex, dir.array(), rayDist, normal); + + // The vertex should be start from rayDist[i] then scale the + // normalizeNormal! + Vector2 intersection = dir[rayIndex] * rayDist[rayIndex] + + Vector2(centroid3d.x, centroid3d.y); + + // outer ring of points, expanded based upon height of each ray intersection + float expansionDist = rayHeight[rayIndex] * heightFactor * + geomFactor; + AlphaVertex::set(&shadowVertices[rayIndex], + intersection.x + normal.x * expansionDist, + intersection.y + normal.y * expansionDist, + 0.0f); + + // inner ring of points + float opacity = 1.0 / (1 + rayHeight[rayIndex] * heightFactor); + AlphaVertex::set(&shadowVertices[rayIndex + rays], + intersection.x, + intersection.y, + opacity); } - float centroidAlpha = 1.0 / (1 + centroid3d.z / heightFactor); - AlphaVertex::set(&shadowVertices[currentVertexIndex++], + float centroidAlpha = 1.0 / (1 + centroid3d.z * heightFactor); + AlphaVertex::set(&shadowVertices[SHADOW_VERTEX_COUNT - 1], centroid3d.x, centroid3d.y, centroidAlpha); #if DEBUG_SHADOW diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index 1d58d96223d4..f02e59156324 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -705,8 +705,8 @@ void Caches::initTempProperties() { propertyDirtyViewport = false; propertyEnable3d = false; propertyCameraDistance = 1.0f; - propertyAmbientShadowStrength = 0x3f; - propertySpotShadowStrength = 0x3f; + propertyAmbientShadowStrength = 25; + propertySpotShadowStrength = 25; propertyLightPosXScale = 0.5f; propertyLightPosYScale = 0.0f; diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index 0f7648698d27..0c544b85f022 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -491,7 +491,7 @@ void DisplayList::replay(ReplayStateStruct& replayStruct, const int level) { replayStruct.mDrawGlStatus); } -#define SHADOW_DELTA 2.0f +#define SHADOW_DELTA 0.1f template void DisplayList::iterate3dChildren(ChildrenSelectMode mode, OpenGLRenderer& renderer, diff --git a/libs/hwui/ShadowTessellator.cpp b/libs/hwui/ShadowTessellator.cpp index 5469aad11963..f138222c2725 100644 --- a/libs/hwui/ShadowTessellator.cpp +++ b/libs/hwui/ShadowTessellator.cpp @@ -40,7 +40,7 @@ void ShadowTessellator::tessellateAmbientShadow(const Vector3* casterPolygon, // A bunch of parameters to tweak the shadow. // TODO: Allow some of these changable by debug settings or APIs. - const float heightFactor = 128; + const float heightFactor = 1.0f / 128; const float geomFactor = 64; AmbientShadow::createAmbientShadow(casterPolygon, casterVertexCount, @@ -69,7 +69,7 @@ void ShadowTessellator::tessellateSpotShadow(const Vector3* casterPolygon, int c reverseReceiverTransform.mapPoint3d(lightCenter); const float lightSize = maximal / 4; - const int lightVertexCount = 16; + const int lightVertexCount = 8; SpotShadow::createSpotShadow(casterPolygon, casterVertexCount, lightCenter, lightSize, lightVertexCount, shadowVertexBuffer); @@ -78,26 +78,23 @@ void ShadowTessellator::tessellateSpotShadow(const Vector3* casterPolygon, int c void ShadowTessellator::generateShadowIndices(uint16_t* shadowIndices) { int currentIndex = 0; - const int layers = SHADOW_LAYER_COUNT; const int rays = SHADOW_RAY_COUNT; // For the penumbra area. - for (int i = 0; i < layers; i++) { - for (int j = 0; j < rays; j++) { - shadowIndices[currentIndex++] = i * rays + j; - shadowIndices[currentIndex++] = (i + 1) * rays + j; - } - // To close the loop, back to the ray 0. - shadowIndices[currentIndex++] = i * rays; - shadowIndices[currentIndex++] = (i + 1) * rays; + for (int i = 0; i < rays; i++) { + shadowIndices[currentIndex++] = i; + shadowIndices[currentIndex++] = rays + i; } - uint16_t base = layers * rays; - uint16_t centroidIndex = (layers + 1) * rays; + // To close the loop, back to the ray 0. + shadowIndices[currentIndex++] = 0; + shadowIndices[currentIndex++] = rays; + + uint16_t centroidIndex = 2 * rays; // For the umbra area, using strips to simulate the fans. - for (int k = 0; k < rays; k++) { - shadowIndices[currentIndex++] = base + k; + for (int i = 0; i < rays; i++) { + shadowIndices[currentIndex++] = rays + i; shadowIndices[currentIndex++] = centroidIndex; } - shadowIndices[currentIndex++] = base; + shadowIndices[currentIndex++] = rays; #if DEBUG_SHADOW if (currentIndex != SHADOW_INDEX_COUNT) { diff --git a/libs/hwui/ShadowTessellator.h b/libs/hwui/ShadowTessellator.h index c49fdcb10627..120774b9e89d 100644 --- a/libs/hwui/ShadowTessellator.h +++ b/libs/hwui/ShadowTessellator.h @@ -46,17 +46,13 @@ namespace uirenderer { // The total number of rays starting from the centroid of shadow area, in order // to generate the shadow geometry. -#define SHADOW_RAY_COUNT 256 - -// The total number of layers in the outer shadow area, 1 being the minimum. -#define SHADOW_LAYER_COUNT 2 +#define SHADOW_RAY_COUNT 128 // The total number of all the vertices representing the shadow. -#define SHADOW_VERTEX_COUNT ((SHADOW_LAYER_COUNT + 1) * SHADOW_RAY_COUNT + 1) +#define SHADOW_VERTEX_COUNT (2 * SHADOW_RAY_COUNT + 1) // The total number of indices used for drawing the shadow geometry as triangle strips. -#define SHADOW_INDEX_COUNT (2 * SHADOW_RAY_COUNT + 1 + 2 * (SHADOW_RAY_COUNT + 1) * \ - SHADOW_LAYER_COUNT) +#define SHADOW_INDEX_COUNT (2 * SHADOW_RAY_COUNT + 1 + 2 * (SHADOW_RAY_COUNT + 1)) class ShadowTessellator { public: diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp index 22d735bff047..54039c017f53 100644 --- a/libs/hwui/SpotShadow.cpp +++ b/libs/hwui/SpotShadow.cpp @@ -29,45 +29,46 @@ namespace android { namespace uirenderer { +static const double EPSILON = 1e-7; + +/** + * Calculate the angle between and x and a y coordinate. + * The atan2 range from -PI to PI. + */ +float angle(const Vector2& point, const Vector2& center) { + return atan2(point.y - center.y, point.x - center.x); +} + /** - * Calculate the intersection of a ray with a polygon. - * It assumes the ray originates inside the polygon. + * Calculate the intersection of a ray with the line segment defined by two points. * - * @param poly The polygon, which is represented in a Vector2 array. - * @param polyLength The length of caster's polygon in terms of number of - * vertices. - * @param point the start of the ray - * @param dx the x vector of the ray - * @param dy the y vector of the ray - * @return the distance along the ray if it intersects with the polygon FP_NAN if otherwise + * Returns a negative value in error conditions. + + * @param rayOrigin The start of the ray + * @param dx The x vector of the ray + * @param dy The y vector of the ray + * @param p1 The first point defining the line segment + * @param p2 The second point defining the line segment + * @return The distance along the ray if it intersects with the line segment, negative if otherwise */ -float SpotShadow::rayIntersectPoly(const Vector2* poly, int polyLength, - const Vector2& point, float dx, float dy) { - double px = point.x; - double py = point.y; - int p1 = polyLength - 1; - for (int p2 = 0; p2 < polyLength; p2++) { - double p1x = poly[p1].x; - double p1y = poly[p1].y; - double p2x = poly[p2].x; - double p2y = poly[p2].y; - // The math below is derived from solving this formula, basically the - // intersection point should stay on both the ray and the edge of (p1, p2). - // solve([p1x+t*(p2x-p1x)=dx*t2+px,p1y+t*(p2y-p1y)=dy*t2+py],[t,t2]); - double div = (dx * (p1y - p2y) + dy * p2x - dy * p1x); - if (div != 0) { - double t = (dx * (p1y - py) + dy * px - dy * p1x) / (div); - if (t >= 0 && t <= 1) { - double t2 = (p1x * (py - p2y) + p2x * (p1y - py) + - px * (p2y - p1y)) / div; - if (t2 > 0) { - return (float)t2; - } - } - } - p1 = p2; - } - return FP_NAN; +float rayIntersectPoints(const Vector2& rayOrigin, float dx, float dy, + const Vector2& p1, const Vector2& p2) { + // The math below is derived from solving this formula, basically the + // intersection point should stay on both the ray and the edge of (p1, p2). + // solve([p1x+t*(p2x-p1x)=dx*t2+px,p1y+t*(p2y-p1y)=dy*t2+py],[t,t2]); + + double divisor = (dx * (p1.y - p2.y) + dy * p2.x - dy * p1.x); + if (divisor == 0) return -1.0f; // error, invalid divisor + +#if DEBUG_SHADOW + double interpVal = (dx * (p1.y - rayOrigin.y) + dy * rayOrigin.x - dy * p1.x) / divisor; + if (interpVal < 0 || interpVal > 1) return -1.0f; // error, doesn't intersect between points +#endif + + double distance = (p1.x * (rayOrigin.y - p2.y) + p2.x * (p1.y - rayOrigin.y) + + rayOrigin.x * (p2.y - p1.y)) / divisor; + + return distance; // may be negative in error cases } /** @@ -131,30 +132,26 @@ int SpotShadow::hull(Vector2* points, int pointsLength, Vector2* retPoly) { lLowerSize--; } } - int count = 0; + // output points in CW ordering + const int total = lUpperSize + lLowerSize - 2; + int outIndex = total - 1; for (int i = 0; i < lUpperSize; i++) { - retPoly[count] = lUpper[i]; - count++; + retPoly[outIndex] = lUpper[i]; + outIndex--; } for (int i = 1; i < lLowerSize - 1; i++) { - retPoly[count] = lLower[i]; - count++; + retPoly[outIndex] = lLower[i]; + outIndex--; } // TODO: Add test harness which verify that all the points are inside the hull. - return count; + return total; } /** * Test whether the 3 points form a counter clockwise turn. * - * @param ax the x coordinate of point a - * @param ay the y coordinate of point a - * @param bx the x coordinate of point b - * @param by the y coordinate of point b - * @param cx the x coordinate of point c - * @param cy the y coordinate of point c * @return true if a right hand turn */ bool SpotShadow::ccw(double ax, double ay, double bx, double by, @@ -303,15 +300,6 @@ void SpotShadow::sort(Vector2* poly, int polyLength, const Vector2& center) { } /** - * Calculate the angle between and x and a y coordinate. - * The atan2 range from -PI to PI, if we want to sort the vertices as clockwise, - * we just negate the return angle. - */ -float SpotShadow::angle(const Vector2& point, const Vector2& center) { - return -(float)atan2(point.y - center.y, point.x - center.x); -} - -/** * Swap points pointed to by i and j */ void SpotShadow::swap(Vector2* points, int i, int j) { @@ -329,10 +317,10 @@ void SpotShadow::quicksortCirc(Vector2* points, int low, int high, int p = low + (high - low) / 2; float pivot = angle(points[p], center); while (i <= j) { - while (angle(points[i], center) < pivot) { + while (angle(points[i], center) > pivot) { i++; } - while (angle(points[j], center) > pivot) { + while (angle(points[j], center) < pivot) { j--; } @@ -508,8 +496,8 @@ void SpotShadow::computeLightPolygon(int points, const Vector3& lightCenter, // TODO: Caching all the sin / cos values and store them in a look up table. for (int i = 0; i < points; i++) { double angle = 2 * i * M_PI / points; - ret[i].x = sinf(angle) * size + lightCenter.x; - ret[i].y = cosf(angle) * size + lightCenter.y; + ret[i].x = cosf(angle) * size + lightCenter.x; + ret[i].y = sinf(angle) * size + lightCenter.y; ret[i].z = lightCenter.z; } } @@ -657,6 +645,63 @@ void SpotShadow::computeSpotShadow(const Vector3* lightPoly, int lightPolyLength } /** + * Converts a polygon specified with CW vertices into an array of distance-from-centroid values. + * + * Returns false in error conditions + * + * @param poly Array of vertices. Note that these *must* be CW. + * @param polyLength The number of vertices in the polygon. + * @param polyCentroid The centroid of the polygon, from which rays will be cast + * @param rayDist The output array for the calculated distances, must be SHADOW_RAY_COUNT in size + */ +bool convertPolyToRayDist(const Vector2* poly, int polyLength, const Vector2& polyCentroid, + float* rayDist) { + const int rays = SHADOW_RAY_COUNT; + const float step = M_PI * 2 / rays; + + const Vector2* lastVertex = &(poly[polyLength - 1]); + float startAngle = angle(*lastVertex, polyCentroid); + + // Start with the ray that's closest to and less than startAngle + int rayIndex = floor((startAngle - EPSILON) / step); + rayIndex = (rayIndex + rays) % rays; // ensure positive + + for (int polyIndex = 0; polyIndex < polyLength; polyIndex++) { + /* + * For a given pair of vertices on the polygon, poly[i-1] and poly[i], the rays that + * intersect these will be those that are between the two angles from the centroid that the + * vertices define. + * + * Because the polygon vertices are stored clockwise, the closest ray with an angle + * *smaller* than that defined by angle(poly[i], centroid) will be the first ray that does + * not intersect with poly[i-1], poly[i]. + */ + float currentAngle = angle(poly[polyIndex], polyCentroid); + + // find first ray that will not intersect the line segment poly[i-1] & poly[i] + int firstRayIndexOnNextSegment = floor((currentAngle - EPSILON) / step); + firstRayIndexOnNextSegment = (firstRayIndexOnNextSegment + rays) % rays; // ensure positive + + // Iterate through all rays that intersect with poly[i-1], poly[i] line segment. + // This may be 0 rays. + while (rayIndex != firstRayIndexOnNextSegment) { + float distanceToIntersect = rayIntersectPoints(polyCentroid, + cos(rayIndex * step), + sin(rayIndex * step), + *lastVertex, poly[polyIndex]); + if (distanceToIntersect < 0) return false; // error case, abort + + rayDist[rayIndex] = distanceToIntersect; + + rayIndex = (rayIndex - 1 + rays) % rays; + } + lastVertex = &poly[polyIndex]; + } + + return true; +} + +/** * Generate a triangle strip given two convex polygons * * @param penumbra The outer polygon x,y vertexes @@ -669,10 +714,9 @@ void SpotShadow::computeSpotShadow(const Vector3* lightPoly, int lightPolyLength void SpotShadow::generateTriangleStrip(const Vector2* penumbra, int penumbraLength, const Vector2* umbra, int umbraLength, VertexBuffer& shadowTriangleStrip) { const int rays = SHADOW_RAY_COUNT; - const int layers = SHADOW_LAYER_COUNT; - int size = rays * (layers + 1); - float step = M_PI * 2 / rays; + const int size = 2 * rays; + const float step = M_PI * 2 / rays; // Centroid of the umbra. Vector2 centroid = ShadowTessellator::centroid2d(umbra, umbraLength); #if DEBUG_SHADOW @@ -683,48 +727,31 @@ void SpotShadow::generateTriangleStrip(const Vector2* penumbra, int penumbraLeng // Intersection to the umbra. float umbraDistPerRay[rays]; - for (int i = 0; i < rays; i++) { - // TODO: Setup a lookup table for all the sin/cos. - float dx = sinf(step * i); - float dy = cosf(step * i); - umbraDistPerRay[i] = rayIntersectPoly(umbra, umbraLength, centroid, - dx, dy); - if (isnan(umbraDistPerRay[i])) { - ALOGE("rayIntersectPoly returns NAN"); - return; - } - penumbraDistPerRay[i] = rayIntersectPoly(penumbra, penumbraLength, - centroid, dx, dy); - if (isnan(umbraDistPerRay[i])) { - ALOGE("rayIntersectPoly returns NAN"); - return; - } - } + // convert CW polygons to ray distance encoding, aborting on conversion failure + if (!convertPolyToRayDist(umbra, umbraLength, centroid, umbraDistPerRay)) return; + if (!convertPolyToRayDist(penumbra, penumbraLength, centroid, penumbraDistPerRay)) return; - int stripSize = getStripSize(rays, layers); - AlphaVertex* shadowVertices = shadowTriangleStrip.alloc(stripSize); - int currentIndex = 0; + AlphaVertex* shadowVertices = shadowTriangleStrip.alloc(getStripSize(rays)); // Calculate the vertices (x, y, alpha) in the shadow area. - for (int layerIndex = 0; layerIndex <= layers; layerIndex++) { - for (int rayIndex = 0; rayIndex < rays; rayIndex++) { - float dx = sinf(step * rayIndex); - float dy = cosf(step * rayIndex); - float layerRatio = layerIndex / (float) layers; - float deltaDist = layerRatio * - (umbraDistPerRay[rayIndex] - penumbraDistPerRay[rayIndex]); - float currentDist = penumbraDistPerRay[rayIndex] + deltaDist; - float op = calculateOpacity(layerRatio); - AlphaVertex::set(&shadowVertices[currentIndex++], - dx * currentDist + centroid.x, dy * currentDist + centroid.y, op); - } + for (int rayIndex = 0; rayIndex < rays; rayIndex++) { + float dx = cosf(step * rayIndex); + float dy = sinf(step * rayIndex); + + // outer ring + float currentDist = penumbraDistPerRay[rayIndex]; + AlphaVertex::set(&shadowVertices[rayIndex], + dx * currentDist + centroid.x, dy * currentDist + centroid.y, 0.0f); + + // inner ring + float deltaDist = umbraDistPerRay[rayIndex] - penumbraDistPerRay[rayIndex]; + currentDist += deltaDist; + AlphaVertex::set(&shadowVertices[rays + rayIndex], + dx * currentDist + centroid.x, dy * currentDist + centroid.y, 1.0f); } // The centroid is in the umbra area, so the opacity is considered as 1.0. - AlphaVertex::set(&shadowVertices[currentIndex++], centroid.x, centroid.y, 1.0); + AlphaVertex::set(&shadowVertices[SHADOW_VERTEX_COUNT - 1], centroid.x, centroid.y, 1.0f); #if DEBUG_SHADOW - if (currentIndex != SHADOW_VERTEX_COUNT) { - ALOGE("number of vertex generated for spot shadow is wrong!"); - } for (int i = 0; i < currentIndex; i++) { ALOGD("spot shadow value: i %d, (x:%f, y:%f, a:%f)", i, shadowVertices[i].x, shadowVertices[i].y, shadowVertices[i].alpha); @@ -754,26 +781,14 @@ void SpotShadow::smoothPolygon(int level, int rays, float* rayDist) { } /** - * Calculate the opacity according to the distance. Ideally, the opacity is 1.0 - * in the umbra area, and fall off to 0.0 till the edge of penumbra area. - * - * @param layerRatio The distance ratio of current sample between umbra and penumbra area. - * Penumbra edge is 0 and umbra edge is 1. - * @return The opacity according to the distance between umbra and penumbra. - */ -float SpotShadow::calculateOpacity(float layerRatio) { - return (layerRatio * layerRatio + layerRatio) / 2.0; -} - -/** * Calculate the number of vertex we will create given a number of rays and layers * * @param rays number of points around the polygons you want * @param layers number of layers of triangle strips you need * @return number of vertex (multiply by 3 for number of floats) */ -int SpotShadow::getStripSize(int rays, int layers) { - return (2 + rays + ((layers) * 2 * (rays + 1))); +int SpotShadow::getStripSize(int rays) { + return (2 + rays + (2 * (rays + 1))); } #if DEBUG_SHADOW @@ -898,7 +913,3 @@ void SpotShadow::testIntersection(const Vector2* poly1, int poly1Length, }; // namespace uirenderer }; // namespace android - - - - diff --git a/libs/hwui/SpotShadow.h b/libs/hwui/SpotShadow.h index 6727eace6330..7839dc3ebb8c 100644 --- a/libs/hwui/SpotShadow.h +++ b/libs/hwui/SpotShadow.h @@ -38,9 +38,8 @@ private: static void computeLightPolygon(int points, const Vector3& lightCenter, float size, Vector3* ret); - static int getStripSize(int rays, int layers); + static int getStripSize(int rays); static void smoothPolygon(int level, int rays, float* rayDist); - static float calculateOpacity(float jf); static float rayIntersectPoly(const Vector2* poly, int polyLength, const Vector2& point, float dx, float dy); @@ -50,7 +49,6 @@ private: static int intersection(Vector2* poly1, int poly1length, Vector2* poly2, int poly2length); static void sort(Vector2* poly, int polyLength, const Vector2& center); - static float angle(const Vector2& point, const Vector2& center); static void swap(Vector2* points, int i, int j); static void quicksortCirc(Vector2* points, int low, int high, const Vector2& center); static void quicksortX(Vector2* points, int low, int high); @@ -65,8 +63,6 @@ private: static void generateTriangleStrip(const Vector2* penumbra, int penumbraLength, const Vector2* umbra, int umbraLength, VertexBuffer& retstrips); - static const double EPSILON = 1e-7; - #if DEBUG_SHADOW // Verification utility function. static bool testConvex(const Vector2* polygon, int polygonLength, -- 2.11.0