Add ear-clipping code to triangulate simple polygons.
Use this to fill concave shadows. Bug: skia:7971 Change-Id: I63dc1ed845f9fa3fcd86f1ad13b03da23cae0313 Reviewed-on: https://skia-review.googlesource.com/135200 Commit-Queue: Jim Van Verth <jvanverth@google.com> Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
parent
a5e703043f
commit
8664a1d7d7
@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "gm.h"
|
||||
#include "SkOffsetPolygon.h"
|
||||
#include "SkPolyUtils.h"
|
||||
#include "SkPathPriv.h"
|
||||
|
||||
static void create_ngon(int n, SkPoint* pts, SkScalar width, SkScalar height) {
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
#include "gm.h"
|
||||
#include "sk_tool_utils.h"
|
||||
#include "SkOffsetPolygon.h"
|
||||
#include "SkPolyUtils.h"
|
||||
#include "SkPathPriv.h"
|
||||
|
||||
static void create_ngon(int n, SkPoint* pts, SkScalar w, SkScalar h, SkPath::Direction dir) {
|
||||
|
@ -47,8 +47,6 @@ skia_utils_sources = [
|
||||
"$_src/utils/SkMultiPictureDocument.cpp",
|
||||
"$_src/utils/SkNWayCanvas.cpp",
|
||||
"$_src/utils/SkNullCanvas.cpp",
|
||||
"$_src/utils/SkOffsetPolygon.cpp",
|
||||
"$_src/utils/SkOffsetPolygon.h",
|
||||
"$_src/utils/SkOSPath.cpp",
|
||||
"$_src/utils/SkOSPath.h",
|
||||
"$_src/utils/SkPaintFilterCanvas.cpp",
|
||||
@ -57,6 +55,8 @@ skia_utils_sources = [
|
||||
"$_src/utils/SkParsePath.cpp",
|
||||
"$_src/utils/SkPatchUtils.cpp",
|
||||
"$_src/utils/SkPatchUtils.h",
|
||||
"$_src/utils/SkPolyUtils.cpp",
|
||||
"$_src/utils/SkPolyUtils.h",
|
||||
"$_src/utils/SkShadowTessellator.cpp",
|
||||
"$_src/utils/SkShadowTessellator.h",
|
||||
"$_src/utils/SkShadowUtils.cpp",
|
||||
|
@ -5,12 +5,16 @@
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "SkOffsetPolygon.h"
|
||||
#include "SkPolyUtils.h"
|
||||
|
||||
#include "SkPointPriv.h"
|
||||
#include "SkTArray.h"
|
||||
#include "SkTemplates.h"
|
||||
#include "SkTDPQueue.h"
|
||||
#include "SkTInternalLList.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// Helper data structures and functions
|
||||
|
||||
struct OffsetSegment {
|
||||
SkPoint fP0;
|
||||
@ -33,23 +37,17 @@ static int compute_side(const SkPoint& s0, const SkPoint& s1, const SkPoint& p)
|
||||
|
||||
// returns 1 for ccw, -1 for cw and 0 if degenerate
|
||||
static int get_winding(const SkPoint* polygonVerts, int polygonSize) {
|
||||
SkPoint p0 = polygonVerts[0];
|
||||
SkPoint p1 = polygonVerts[1];
|
||||
|
||||
for (int i = 2; i < polygonSize; ++i) {
|
||||
SkPoint p2 = polygonVerts[i];
|
||||
|
||||
// determine if cw or ccw
|
||||
int side = compute_side(p0, p1, p2);
|
||||
if (0 != side) {
|
||||
return ((side > 0) ? 1 : -1);
|
||||
}
|
||||
|
||||
// if nearly collinear, treat as straight line and continue
|
||||
p1 = p2;
|
||||
// compute area and use sign to determine winding
|
||||
SkScalar quadArea = 0;
|
||||
for (int curr = 0; curr < polygonSize; ++curr) {
|
||||
int next = (curr + 1) % polygonSize;
|
||||
quadArea += polygonVerts[curr].cross(polygonVerts[next]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (SkScalarNearlyZero(quadArea)) {
|
||||
return 0;
|
||||
}
|
||||
// 1 == ccw, -1 == cw
|
||||
return (quadArea > 0) ? 1 : -1;
|
||||
}
|
||||
|
||||
// Helper function to compute the individual vector for non-equal offsets
|
||||
@ -261,6 +259,8 @@ struct EdgeData {
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The objective here is to inset all of the edges by the given distance, and then
|
||||
// remove any invalid inset edges by detecting right-hand turns. In a ccw polygon,
|
||||
// we should only be making left-hand turns (for cw polygons, we use the winding
|
||||
@ -280,6 +280,7 @@ bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize
|
||||
return false;
|
||||
}
|
||||
|
||||
// get winding direction
|
||||
int winding = get_winding(inputPolygonVerts, inputPolygonSize);
|
||||
if (0 == winding) {
|
||||
return false;
|
||||
@ -397,9 +398,11 @@ bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize
|
||||
return (insetPolygon->count() >= 3 && is_convex(*insetPolygon));
|
||||
}
|
||||
|
||||
// compute the number of points needed for a circular join when offsetting a reflex vertex
|
||||
static void compute_radial_steps(const SkVector& v1, const SkVector& v2, SkScalar r,
|
||||
SkScalar* rotSin, SkScalar* rotCos, int* n) {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// compute the number of points needed for a circular join when offsetting a reflex vertex
|
||||
void SkComputeRadialSteps(const SkVector& v1, const SkVector& v2, SkScalar r,
|
||||
SkScalar* rotSin, SkScalar* rotCos, int* n) {
|
||||
const SkScalar kRecipPixelsPerArcSegment = 0.25f;
|
||||
|
||||
SkScalar rCos = v1.dot(v2);
|
||||
@ -529,10 +532,10 @@ public:
|
||||
|
||||
// if we intersect with the edge above or below us
|
||||
// then we know this polygon is not simple, so don't remove, just fail
|
||||
if (removeIndex > 0 && fEdges[removeIndex].intersect(fEdges[removeIndex-1])) {
|
||||
if (removeIndex > 0 && fEdges[removeIndex].intersect(fEdges[removeIndex - 1])) {
|
||||
return false;
|
||||
}
|
||||
if (removeIndex < fEdges.count()-1) {
|
||||
if (removeIndex < fEdges.count() - 1) {
|
||||
if (fEdges[removeIndex].intersect(fEdges[removeIndex + 1])) {
|
||||
return false;
|
||||
}
|
||||
@ -555,7 +558,7 @@ private:
|
||||
// Then as we pop the vertices from the queue we generate events which indicate that an edge
|
||||
// should be added or removed from an edge list. If any intersections are detected in the edge
|
||||
// list, then we know the polygon is self-intersecting and hence not simple.
|
||||
static bool is_simple_polygon(const SkPoint* polygon, int polygonSize) {
|
||||
bool SkIsSimplePolygon(const SkPoint* polygon, int polygonSize) {
|
||||
SkTDPQueue <Vertex, Vertex::Left> vertexQueue;
|
||||
EdgeList sweepLine;
|
||||
|
||||
@ -613,7 +616,8 @@ static bool is_simple_polygon(const SkPoint* polygon, int polygonSize) {
|
||||
return (vertexQueue.count() == 0);
|
||||
}
|
||||
|
||||
// TODO: assuming a constant offset here -- do we want to support variable offset?
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize,
|
||||
std::function<SkScalar(const SkPoint&)> offsetDistanceFunc,
|
||||
SkTDArray<SkPoint>* offsetPolygon, SkTDArray<int>* polygonIndices) {
|
||||
@ -621,22 +625,12 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_simple_polygon(inputPolygonVerts, inputPolygonSize)) {
|
||||
// get winding direction
|
||||
int winding = get_winding(inputPolygonVerts, inputPolygonSize);
|
||||
if (0 == winding) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// compute area and use sign to determine winding
|
||||
SkScalar quadArea = 0;
|
||||
for (int curr = 0; curr < inputPolygonSize; ++curr) {
|
||||
int next = (curr + 1) % inputPolygonSize;
|
||||
quadArea += inputPolygonVerts[curr].cross(inputPolygonVerts[next]);
|
||||
}
|
||||
if (SkScalarNearlyZero(quadArea)) {
|
||||
return false;
|
||||
}
|
||||
// 1 == ccw, -1 == cw
|
||||
int winding = (quadArea > 0) ? 1 : -1;
|
||||
|
||||
// build normals
|
||||
SkAutoSTMalloc<64, SkVector> normal0(inputPolygonSize);
|
||||
SkAutoSTMalloc<64, SkVector> normal1(inputPolygonSize);
|
||||
@ -667,7 +661,7 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz
|
||||
SkScalar rotSin, rotCos;
|
||||
int numSteps;
|
||||
SkVector prevNormal = normal1[currIndex];
|
||||
compute_radial_steps(prevNormal, normal0[currIndex], SkScalarAbs(offset),
|
||||
SkComputeRadialSteps(prevNormal, normal0[currIndex], SkScalarAbs(offset),
|
||||
&rotSin, &rotCos, &numSteps);
|
||||
for (int i = 0; i < numSteps - 1; ++i) {
|
||||
SkVector currNormal = SkVector::Make(prevNormal.fX*rotCos - prevNormal.fY*rotSin,
|
||||
@ -792,14 +786,211 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz
|
||||
}
|
||||
}
|
||||
|
||||
// compute signed area to check winding (it should be same as the original polygon)
|
||||
quadArea = 0;
|
||||
for (int curr = 0; curr < offsetPolygon->count(); ++curr) {
|
||||
int next = (curr + 1) % offsetPolygon->count();
|
||||
quadArea += (*offsetPolygon)[curr].cross((*offsetPolygon)[next]);
|
||||
}
|
||||
// check winding of offset polygon (it should be same as the original polygon)
|
||||
SkScalar offsetWinding = get_winding(offsetPolygon->begin(), offsetPolygon->count());
|
||||
|
||||
return (winding*quadArea > 0 &&
|
||||
is_simple_polygon(offsetPolygon->begin(), offsetPolygon->count()));
|
||||
return (winding*offsetWinding > 0 &&
|
||||
SkIsSimplePolygon(offsetPolygon->begin(), offsetPolygon->count()));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct TriangulationVertex {
|
||||
SK_DECLARE_INTERNAL_LLIST_INTERFACE(TriangulationVertex);
|
||||
|
||||
enum class VertexType { kConvex, kReflex };
|
||||
|
||||
SkPoint fPosition;
|
||||
VertexType fVertexType;
|
||||
uint16_t fIndex;
|
||||
uint16_t fPrevIndex;
|
||||
uint16_t fNextIndex;
|
||||
};
|
||||
|
||||
// test to see if point p is in triangle p0p1p2.
|
||||
// for now assuming strictly inside -- if on the edge it's outside
|
||||
static bool point_in_triangle(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
|
||||
const SkPoint& p) {
|
||||
SkVector v0 = p1 - p0;
|
||||
SkVector v1 = p2 - p1;
|
||||
SkScalar n = v0.cross(v1);
|
||||
|
||||
SkVector w0 = p - p0;
|
||||
if (n*v0.cross(w0) < SK_ScalarNearlyZero) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SkVector w1 = p - p1;
|
||||
if (n*v1.cross(w1) < SK_ScalarNearlyZero) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SkVector v2 = p0 - p2;
|
||||
SkVector w2 = p - p2;
|
||||
if (n*v2.cross(w2) < SK_ScalarNearlyZero) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Data structure to track reflex vertices and check whether any are inside a given triangle
|
||||
class ReflexHash {
|
||||
public:
|
||||
void add(TriangulationVertex* v) {
|
||||
fReflexList.addToTail(v);
|
||||
}
|
||||
|
||||
void remove(TriangulationVertex* v) {
|
||||
fReflexList.remove(v);
|
||||
}
|
||||
|
||||
bool checkTriangle(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
|
||||
for (SkTInternalLList<TriangulationVertex>::Iter reflexIter = fReflexList.begin();
|
||||
reflexIter != fReflexList.end(); ++reflexIter) {
|
||||
TriangulationVertex* reflexVertex = *reflexIter;
|
||||
if (point_in_triangle(p0, p1, p2, reflexVertex->fPosition)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO: switch to an actual spatial hash
|
||||
SkTInternalLList<TriangulationVertex> fReflexList;
|
||||
};
|
||||
|
||||
// Check to see if a reflex vertex has become a convex vertex after clipping an ear
|
||||
static void reclassify_vertex(TriangulationVertex* p, const SkPoint* polygonVerts,
|
||||
int winding, ReflexHash* reflexHash,
|
||||
SkTInternalLList<TriangulationVertex>* convexList) {
|
||||
if (TriangulationVertex::VertexType::kReflex == p->fVertexType) {
|
||||
SkVector v0 = p->fPosition - polygonVerts[p->fPrevIndex];
|
||||
SkVector v1 = polygonVerts[p->fNextIndex] - p->fPosition;
|
||||
if (winding*v0.cross(v1) > SK_ScalarNearlyZero) {
|
||||
p->fVertexType = TriangulationVertex::VertexType::kConvex;
|
||||
reflexHash->remove(p);
|
||||
p->fPrev = p->fNext = nullptr;
|
||||
convexList->addToTail(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SkTriangulateSimplePolygon(const SkPoint* polygonVerts, uint16_t* indexMap, int polygonSize,
|
||||
SkTDArray<uint16_t>* triangleIndices) {
|
||||
if (polygonSize < 3) {
|
||||
return false;
|
||||
}
|
||||
// need to be able to represent all the vertices in the 16-bit indices
|
||||
if (polygonSize >= (1 << 16)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get winding direction
|
||||
// TODO: we do this for all the polygon routines -- might be better to have the client
|
||||
// compute it and pass it in
|
||||
int winding = get_winding(polygonVerts, polygonSize);
|
||||
if (0 == winding) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Classify initial vertices into a list of convex vertices and a hash of reflex vertices
|
||||
// TODO: possibly sort the convexList in some way to get better triangles
|
||||
SkTInternalLList<TriangulationVertex> convexList;
|
||||
ReflexHash reflexHash;
|
||||
SkAutoSTMalloc<64, TriangulationVertex> triangulationVertices(polygonSize);
|
||||
int prevIndex = polygonSize - 1;
|
||||
int currIndex = 0;
|
||||
int nextIndex = 1;
|
||||
SkVector v0 = polygonVerts[currIndex] - polygonVerts[prevIndex];
|
||||
SkVector v1 = polygonVerts[nextIndex] - polygonVerts[currIndex];
|
||||
for (int i = 0; i < polygonSize; ++i) {
|
||||
SkDEBUGCODE(memset(&triangulationVertices[currIndex], 0, sizeof(TriangulationVertex)));
|
||||
triangulationVertices[currIndex].fPosition = polygonVerts[currIndex];
|
||||
triangulationVertices[currIndex].fIndex = currIndex;
|
||||
triangulationVertices[currIndex].fPrevIndex = prevIndex;
|
||||
triangulationVertices[currIndex].fNextIndex = nextIndex;
|
||||
if (winding*v0.cross(v1) > SK_ScalarNearlyZero) {
|
||||
triangulationVertices[currIndex].fVertexType = TriangulationVertex::VertexType::kConvex;
|
||||
convexList.addToTail(&triangulationVertices[currIndex]);
|
||||
} else {
|
||||
// We treat near collinear vertices as reflex
|
||||
triangulationVertices[currIndex].fVertexType = TriangulationVertex::VertexType::kReflex;
|
||||
reflexHash.add(&triangulationVertices[currIndex]);
|
||||
}
|
||||
|
||||
prevIndex = currIndex;
|
||||
currIndex = nextIndex;
|
||||
nextIndex = (currIndex + 1) % polygonSize;
|
||||
v0 = v1;
|
||||
v1 = polygonVerts[nextIndex] - polygonVerts[currIndex];
|
||||
}
|
||||
|
||||
// The general concept: We are trying to find three neighboring vertices where
|
||||
// no other vertex lies inside the triangle (an "ear"). If we find one, we clip
|
||||
// that ear off, and then repeat on the new polygon. Once we get down to three vertices
|
||||
// we have triangulated the entire polygon.
|
||||
// In the worst case this is an n^2 algorithm. We can cut down the search space somewhat by
|
||||
// noting that only convex vertices can be potential ears, and we only need to check whether
|
||||
// any reflex vertices lie inside the ear.
|
||||
triangleIndices->setReserve(triangleIndices->count() + 3 * (polygonSize - 2));
|
||||
int vertexCount = polygonSize;
|
||||
while (vertexCount > 3) {
|
||||
bool success = false;
|
||||
TriangulationVertex* earVertex = nullptr;
|
||||
TriangulationVertex* p0 = nullptr;
|
||||
TriangulationVertex* p2 = nullptr;
|
||||
// find a convex vertex to clip
|
||||
for (SkTInternalLList<TriangulationVertex>::Iter convexIter = convexList.begin();
|
||||
convexIter != convexList.end(); ++convexIter) {
|
||||
earVertex = *convexIter;
|
||||
SkASSERT(TriangulationVertex::VertexType::kReflex != earVertex->fVertexType);
|
||||
|
||||
p0 = &triangulationVertices[earVertex->fPrevIndex];
|
||||
p2 = &triangulationVertices[earVertex->fNextIndex];
|
||||
|
||||
// see if any reflex vertices are inside the ear
|
||||
bool failed = reflexHash.checkTriangle(p0->fPosition, earVertex->fPosition,
|
||||
p2->fPosition);
|
||||
if (failed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// found one we can clip
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
// If we can't find any ears to clip, this probably isn't a simple polygon
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add indices
|
||||
auto indices = triangleIndices->append(3);
|
||||
indices[0] = indexMap[p0->fIndex];
|
||||
indices[1] = indexMap[earVertex->fIndex];
|
||||
indices[2] = indexMap[p2->fIndex];
|
||||
|
||||
// clip the ear
|
||||
convexList.remove(earVertex);
|
||||
--vertexCount;
|
||||
|
||||
// reclassify reflex verts
|
||||
p0->fNextIndex = earVertex->fNextIndex;
|
||||
reclassify_vertex(p0, polygonVerts, winding, &reflexHash, &convexList);
|
||||
|
||||
p2->fPrevIndex = earVertex->fPrevIndex;
|
||||
reclassify_vertex(p2, polygonVerts, winding, &reflexHash, &convexList);
|
||||
}
|
||||
|
||||
// output indices
|
||||
for (SkTInternalLList<TriangulationVertex>::Iter vertexIter = convexList.begin();
|
||||
vertexIter != convexList.end(); ++vertexIter) {
|
||||
TriangulationVertex* vertex = *vertexIter;
|
||||
*triangleIndices->push() = indexMap[vertex->fIndex];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -49,6 +49,7 @@ inline bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPoly
|
||||
/**
|
||||
* Generates a simple polygon (if possible) that is offset a variable distance (controlled by
|
||||
* offsetDistanceFunc) from the boundary of a given simple polygon.
|
||||
* The input polygon must be simple and have no coincident vertices or collinear edges.
|
||||
*
|
||||
* @param inputPolygonVerts Array of points representing the vertices of the original polygon.
|
||||
* @param inputPolygonSize Number of vertices in the original polygon.
|
||||
@ -66,6 +67,7 @@ bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPolygonSiz
|
||||
/**
|
||||
* Generates a simple polygon (if possible) that is offset a constant distance from the boundary
|
||||
* of a given simple polygon.
|
||||
* The input polygon must be simple and have no coincident vertices or collinear edges.
|
||||
*
|
||||
* @param inputPolygonVerts Array of points representing the vertices of the original polygon.
|
||||
* @param inputPolygonSize Number of vertices in the original polygon.
|
||||
@ -100,4 +102,42 @@ inline bool SkOffsetSimplePolygon(const SkPoint* inputPolygonVerts, int inputPol
|
||||
bool SkOffsetSegment(const SkPoint& p0, const SkPoint& p1, SkScalar d0, SkScalar d1,
|
||||
int side, SkPoint* offset0, SkPoint* offset1);
|
||||
|
||||
/**
|
||||
* Compute the number of points needed for a circular join when offsetting a vertex.
|
||||
* The lengths of offset0 and offset1 don't have to equal r -- only the direction matters.
|
||||
* The segment lengths will be approximately four pixels.
|
||||
*
|
||||
* @param offset0 Starting offset vector direction.
|
||||
* @param offset1 Ending offset vector direction.
|
||||
* @param r Length of offset.
|
||||
* @param rotSin Sine of rotation delta per step.
|
||||
* @param rotCos Cosine of rotation delta per step.
|
||||
* @param n Number of steps to fill out the arc.
|
||||
*/
|
||||
void SkComputeRadialSteps(const SkVector& offset0, const SkVector& offset1, SkScalar r,
|
||||
SkScalar* rotSin, SkScalar* rotCos, int* n);
|
||||
|
||||
/**
|
||||
* Determine whether a polygon is simple (i.e., not self-intersecting) or not.
|
||||
*
|
||||
* @param polygonVerts Array of points representing the vertices of the polygon.
|
||||
* @param polygonSize Number of vertices in the polygon.
|
||||
* @return true if the polygon is simple, false otherwise.
|
||||
*/
|
||||
bool SkIsSimplePolygon(const SkPoint* polygonVerts, int polygonSize);
|
||||
|
||||
/**
|
||||
* Compute indices to triangulate the given polygon.
|
||||
* The input polygon must be simple (i.e. it is not self-intersecting)
|
||||
* and have no coincident vertices or collinear edges.
|
||||
*
|
||||
* @param polygonVerts Array of points representing the vertices of the polygon.
|
||||
* @param indexMap Mapping from index in the given array to the final index in the triangulation.
|
||||
* @param polygonSize Number of vertices in the polygon.
|
||||
* @param triangleIndices Indices of the resulting triangulation.
|
||||
* @return true if successful, false otherwise.
|
||||
*/
|
||||
bool SkTriangulateSimplePolygon(const SkPoint* polygonVerts, uint16_t* indexMap, int polygonSize,
|
||||
SkTDArray<uint16_t>* triangleIndices);
|
||||
|
||||
#endif
|
@ -9,7 +9,7 @@
|
||||
#include "SkColorData.h"
|
||||
#include "SkDrawShadowInfo.h"
|
||||
#include "SkGeometry.h"
|
||||
#include "SkOffsetPolygon.h"
|
||||
#include "SkPolyUtils.h"
|
||||
#include "SkPath.h"
|
||||
#include "SkPoint3.h"
|
||||
#include "SkPointPriv.h"
|
||||
@ -127,21 +127,6 @@ static bool compute_normal(const SkPoint& p0, const SkPoint& p1, SkScalar dir,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void compute_radial_steps(const SkVector& v1, const SkVector& v2, SkScalar r,
|
||||
SkScalar* rotSin, SkScalar* rotCos, int* n) {
|
||||
const SkScalar kRecipPixelsPerArcSegment = 0.125f;
|
||||
|
||||
SkScalar rCos = v1.dot(v2);
|
||||
SkScalar rSin = v1.cross(v2);
|
||||
SkScalar theta = SkScalarATan2(rSin, rCos);
|
||||
|
||||
int steps = SkScalarRoundToInt(SkScalarAbs(r*theta*kRecipPixelsPerArcSegment));
|
||||
|
||||
SkScalar dTheta = theta / steps;
|
||||
*rotSin = SkScalarSinCos(dTheta, rotCos);
|
||||
*n = steps;
|
||||
}
|
||||
|
||||
static bool duplicate_pt(const SkPoint& p0, const SkPoint& p1) {
|
||||
static constexpr SkScalar kClose = (SK_Scalar1 / 16);
|
||||
static constexpr SkScalar kCloseSqd = kClose * kClose;
|
||||
@ -269,6 +254,9 @@ void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray<SkPoint>& umbra
|
||||
SkTDArray<int>* umbraIndices,
|
||||
const SkTDArray<SkPoint>& penumbraPolygon,
|
||||
SkTDArray<int>* penumbraIndices) {
|
||||
// TODO: only create and fill indexMap when fTransparent is true?
|
||||
SkAutoSTMalloc<64, uint16_t> indexMap(umbraPolygon.count());
|
||||
|
||||
// find minimum indices
|
||||
int minIndex = 0;
|
||||
int min = (*penumbraIndices)[0];
|
||||
@ -311,6 +299,7 @@ void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray<SkPoint>& umbra
|
||||
*fPositions.push() = umbraPolygon[currUmbra];
|
||||
*fColors.push() = fUmbraColor;
|
||||
fPrevUmbraIndex = 1;
|
||||
indexMap[currUmbra] = 1;
|
||||
|
||||
int nextPenumbra = (currPenumbra + 1) % penumbraPolygon.count();
|
||||
int nextUmbra = (currUmbra + 1) % umbraPolygon.count();
|
||||
@ -326,6 +315,7 @@ void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray<SkPoint>& umbra
|
||||
*fPositions.push() = umbraPolygon[nextUmbra];
|
||||
*fColors.push() = fUmbraColor;
|
||||
int currUmbraIndex = fPositions.count() - 1;
|
||||
indexMap[nextUmbra] = currUmbraIndex;
|
||||
|
||||
this->appendQuad(prevPenumbraIndex, currPenumbraIndex,
|
||||
fPrevUmbraIndex, currUmbraIndex);
|
||||
@ -363,6 +353,7 @@ void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray<SkPoint>& umbra
|
||||
*fPositions.push() = umbraPolygon[nextUmbra];
|
||||
*fColors.push() = fUmbraColor;
|
||||
int currUmbraIndex = fPositions.count() - 1;
|
||||
indexMap[nextUmbra] = currUmbraIndex;
|
||||
|
||||
this->appendTriangle(fPrevUmbraIndex, prevPenumbraIndex, currUmbraIndex);
|
||||
|
||||
@ -381,12 +372,14 @@ void SkBaseShadowTessellator::stitchConcaveRings(const SkTDArray<SkPoint>& umbra
|
||||
*fPositions.push() = umbraPolygon[nextUmbra];
|
||||
*fColors.push() = fUmbraColor;
|
||||
int currUmbraIndex = fPositions.count() - 1;
|
||||
indexMap[nextUmbra] = currUmbraIndex;
|
||||
|
||||
this->appendQuad(prevPenumbraIndex, currPenumbraIndex,
|
||||
fPrevUmbraIndex, currUmbraIndex);
|
||||
|
||||
if (fTransparent) {
|
||||
// TODO: fill penumbra
|
||||
SkTriangulateSimplePolygon(umbraPolygon.begin(), indexMap, umbraPolygon.count(),
|
||||
&fIndices);
|
||||
}
|
||||
}
|
||||
|
||||
@ -508,7 +501,7 @@ bool SkBaseShadowTessellator::addArc(const SkVector& nextNormal, bool finishArc)
|
||||
// fill in fan from previous quad
|
||||
SkScalar rotSin, rotCos;
|
||||
int numSteps;
|
||||
compute_radial_steps(fPrevOutset, nextNormal, fRadius, &rotSin, &rotCos, &numSteps);
|
||||
SkComputeRadialSteps(fPrevOutset, nextNormal, fRadius, &rotSin, &rotCos, &numSteps);
|
||||
SkVector prevNormal = fPrevOutset;
|
||||
for (int i = 0; i < numSteps-1; ++i) {
|
||||
SkVector currNormal;
|
||||
@ -801,8 +794,7 @@ bool SkAmbientShadowTessellator::computeConvexShadow() {
|
||||
}
|
||||
|
||||
bool SkAmbientShadowTessellator::computeConcaveShadow() {
|
||||
// TODO: remove when we support filling the penumbra
|
||||
if (fTransparent) {
|
||||
if (!SkIsSimplePolygon(&fPathPolygon[0], fPathPolygon.count())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1418,8 +1410,7 @@ bool SkSpotShadowTessellator::computeConvexShadow(SkScalar radius) {
|
||||
}
|
||||
|
||||
bool SkSpotShadowTessellator::computeConcaveShadow(SkScalar radius) {
|
||||
// TODO: remove when we support filling the penumbra
|
||||
if (fTransparent) {
|
||||
if (!SkIsSimplePolygon(&fPathPolygon[0], fPathPolygon.count())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -542,9 +542,11 @@ static bool validate_rec(const SkDrawShadowRec& rec) {
|
||||
void SkBaseDevice::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) {
|
||||
auto drawVertsProc = [this](const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint,
|
||||
SkScalar tx, SkScalar ty) {
|
||||
SkAutoDeviceCTMRestore adr(this, SkMatrix::Concat(this->ctm(),
|
||||
SkMatrix::MakeTrans(tx, ty)));
|
||||
this->drawVertices(vertices, mode, paint);
|
||||
if (vertices->vertexCount()) {
|
||||
SkAutoDeviceCTMRestore adr(this, SkMatrix::Concat(this->ctm(),
|
||||
SkMatrix::MakeTrans(tx, ty)));
|
||||
this->drawVertices(vertices, mode, paint);
|
||||
}
|
||||
};
|
||||
|
||||
if (!validate_rec(rec)) {
|
||||
|
@ -5,7 +5,7 @@
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
#include "Test.h"
|
||||
#include "SkOffsetPolygon.h"
|
||||
#include "SkPolyUtils.h"
|
||||
|
||||
static bool is_convex(const SkTDArray<SkPoint>& poly) {
|
||||
if (poly.count() < 3) {
|
||||
|
@ -5,7 +5,7 @@
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
#include "Test.h"
|
||||
#include "SkOffsetPolygon.h"
|
||||
#include "SkPolyUtils.h"
|
||||
|
||||
static bool is_convex(const SkTDArray<SkPoint>& poly) {
|
||||
if (poly.count() < 3) {
|
||||
@ -200,10 +200,7 @@ DEF_TEST(OffsetSimplePoly, reporter) {
|
||||
*intersectingPoly.push() = SkPoint::Make(-43.30f, -25.0f);
|
||||
*intersectingPoly.push() = SkPoint::Make(-14.43f, -25.0f);
|
||||
|
||||
result = SkOffsetSimplePolygon(&intersectingPoly[0], intersectingPoly.count(), -100,
|
||||
&offsetPoly);
|
||||
// SkOffsetSimplePolygon now assumes that the input is simple, so we'll just check for that
|
||||
result = SkIsSimplePolygon(&intersectingPoly[0], intersectingPoly.count());
|
||||
REPORTER_ASSERT(reporter, !result);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user