LCOV - code coverage report
Current view: top level - gfx/skia/skia/src/utils - SkInsetConvexPolygon.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 145 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 6 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * Copyright 2017 Google Inc.
       3             :  *
       4             :  * Use of this source code is governed by a BSD-style license that can be
       5             :  * found in the LICENSE file.
       6             :  */
       7             : 
       8             : #include "SkInsetConvexPolygon.h"
       9             : 
      10             : #include "SkTemplates.h"
      11             : 
      12             : struct InsetSegment {
      13             :     SkPoint fP0;
      14             :     SkPoint fP1;
      15             : };
      16             : 
      17             : // Computes perpDot for point compared to segment.
      18             : // A positive value means the point is to the left of the segment,
      19             : // negative is to the right, 0 is collinear.
      20           0 : static int compute_side(const SkPoint& s0, const SkPoint& s1, const SkPoint& p) {
      21           0 :     SkVector v0 = s1 - s0;
      22           0 :     SkVector v1 = p - s0;
      23           0 :     SkScalar perpDot = v0.cross(v1);
      24           0 :     if (!SkScalarNearlyZero(perpDot)) {
      25           0 :         return ((perpDot > 0) ? 1 : -1);
      26             :     }
      27             : 
      28           0 :     return 0;
      29             : }
      30             : 
      31             : // returns 1 for ccw, -1 for cw and 0 if degenerate
      32           0 : static int get_winding(const SkPoint* polygonVerts, int polygonSize) {
      33           0 :     SkPoint p0 = polygonVerts[0];
      34           0 :     SkPoint p1 = polygonVerts[1];
      35             : 
      36           0 :     for (int i = 2; i < polygonSize; ++i) {
      37           0 :         SkPoint p2 = polygonVerts[i];
      38             : 
      39             :         // determine if cw or ccw
      40           0 :         int side = compute_side(p0, p1, p2);
      41           0 :         if (0 != side) {
      42           0 :             return ((side > 0) ? 1 : -1);
      43             :         }
      44             : 
      45             :         // if nearly collinear, treat as straight line and continue
      46           0 :         p1 = p2;
      47             :     }
      48             : 
      49           0 :     return 0;
      50             : }
      51             : 
      52             : // Offset line segment p0-p1 'd0' and 'd1' units in the direction specified by 'side'
      53           0 : bool SkOffsetSegment(const SkPoint& p0, const SkPoint& p1, SkScalar d0, SkScalar d1,
      54             :                      int side, SkPoint* offset0, SkPoint* offset1) {
      55           0 :     SkASSERT(side == -1 || side == 1);
      56           0 :     SkVector perp = SkVector::Make(p0.fY - p1.fY, p1.fX - p0.fX);
      57           0 :     if (SkScalarNearlyEqual(d0, d1)) {
      58             :         // if distances are equal, can just outset by the perpendicular
      59           0 :         perp.setLength(d0*side);
      60           0 :         *offset0 = p0 + perp;
      61           0 :         *offset1 = p1 + perp;
      62             :     } else {
      63             :         // Otherwise we need to compute the outer tangent.
      64             :         // See: http://www.ambrsoft.com/TrigoCalc/Circles2/Circles2Tangent_.htm
      65           0 :         if (d0 < d1) {
      66           0 :             side = -side;
      67             :         }
      68           0 :         SkScalar dD = d0 - d1;
      69             :         // if one circle is inside another, we can't compute an offset
      70           0 :         if (dD*dD >= p0.distanceToSqd(p1)) {
      71           0 :             return false;
      72             :         }
      73           0 :         SkPoint outerTangentIntersect = SkPoint::Make((p1.fX*d0 - p0.fX*d1) / dD,
      74           0 :                                                       (p1.fY*d0 - p0.fY*d1) / dD);
      75             : 
      76           0 :         SkScalar d0sq = d0*d0;
      77           0 :         SkVector dP = outerTangentIntersect - p0;
      78           0 :         SkScalar dPlenSq = dP.lengthSqd();
      79           0 :         SkScalar discrim = SkScalarSqrt(dPlenSq - d0sq);
      80           0 :         offset0->fX = p0.fX + (d0sq*dP.fX - side*d0*dP.fY*discrim) / dPlenSq;
      81           0 :         offset0->fY = p0.fY + (d0sq*dP.fY + side*d0*dP.fX*discrim) / dPlenSq;
      82             : 
      83           0 :         SkScalar d1sq = d1*d1;
      84           0 :         dP = outerTangentIntersect - p1;
      85           0 :         dPlenSq = dP.lengthSqd();
      86           0 :         discrim = SkScalarSqrt(dPlenSq - d1sq);
      87           0 :         offset1->fX = p1.fX + (d1sq*dP.fX - side*d1*dP.fY*discrim) / dPlenSq;
      88           0 :         offset1->fY = p1.fY + (d1sq*dP.fY + side*d1*dP.fX*discrim) / dPlenSq;
      89             :     }
      90             : 
      91           0 :     return true;
      92             : }
      93             : 
      94             : // Compute the intersection 'p' between segments s0 and s1, if any.
      95             : // 's' is the parametric value for the intersection along 's0' & 't' is the same for 's1'.
      96             : // Returns false if there is no intersection.
      97           0 : static bool compute_intersection(const InsetSegment& s0, const InsetSegment& s1,
      98             :                                  SkPoint* p, SkScalar* s, SkScalar* t) {
      99           0 :     SkVector v0 = s0.fP1 - s0.fP0;
     100           0 :     SkVector v1 = s1.fP1 - s1.fP0;
     101             : 
     102           0 :     SkScalar perpDot = v0.cross(v1);
     103           0 :     if (SkScalarNearlyZero(perpDot)) {
     104             :         // segments are parallel
     105             :         // check if endpoints are touching
     106           0 :         if (s0.fP1.equalsWithinTolerance(s1.fP0)) {
     107           0 :             *p = s0.fP1;
     108           0 :             *s = SK_Scalar1;
     109           0 :             *t = 0;
     110           0 :             return true;
     111             :         }
     112           0 :         if (s1.fP1.equalsWithinTolerance(s0.fP0)) {
     113           0 :             *p = s1.fP1;
     114           0 :             *s = 0;
     115           0 :             *t = SK_Scalar1;
     116           0 :             return true;
     117             :         }
     118             : 
     119           0 :         return false;
     120             :     }
     121             : 
     122           0 :     SkVector d = s1.fP0 - s0.fP0;
     123           0 :     SkScalar localS = d.cross(v1) / perpDot;
     124           0 :     if (localS < 0 || localS > SK_Scalar1) {
     125           0 :         return false;
     126             :     }
     127           0 :     SkScalar localT = d.cross(v0) / perpDot;
     128           0 :     if (localT < 0 || localT > SK_Scalar1) {
     129           0 :         return false;
     130             :     }
     131             : 
     132           0 :     v0 *= localS;
     133           0 :     *p = s0.fP0 + v0;
     134           0 :     *s = localS;
     135           0 :     *t = localT;
     136             : 
     137           0 :     return true;
     138             : }
     139             : 
     140             : #ifdef SK_DEBUG
     141           0 : static bool is_convex(const SkTDArray<SkPoint>& poly) {
     142           0 :     if (poly.count() <= 3) {
     143           0 :         return true;
     144             :     }
     145             : 
     146           0 :     SkVector v0 = poly[0] - poly[poly.count() - 1];
     147           0 :     SkVector v1 = poly[1] - poly[poly.count() - 1];
     148           0 :     SkScalar winding = v0.cross(v1);
     149             : 
     150           0 :     for (int i = 0; i < poly.count() - 1; ++i) {
     151           0 :         int j = i + 1;
     152           0 :         int k = (i + 2) % poly.count();
     153             : 
     154           0 :         SkVector v0 = poly[j] - poly[i];
     155           0 :         SkVector v1 = poly[k] - poly[i];
     156           0 :         SkScalar perpDot = v0.cross(v1);
     157           0 :         if (winding*perpDot < 0) {
     158           0 :             return false;
     159             :         }
     160             :     }
     161             : 
     162           0 :     return true;
     163             : }
     164             : #endif
     165             : 
     166             : // The objective here is to inset all of the edges by the given distance, and then
     167             : // remove any invalid inset edges by detecting right-hand turns. In a ccw polygon,
     168             : // we should only be making left-hand turns (for cw polygons, we use the winding
     169             : // parameter to reverse this). We detect this by checking whether the second intersection
     170             : // on an edge is closer to its tail than the first one.
     171             : //
     172             : // We might also have the case that there is no intersection between two neighboring inset edges.
     173             : // In this case, one edge will lie to the right of the other and should be discarded along with
     174             : // its previous intersection (if any).
     175             : //
     176             : // Note: the assumption is that inputPolygon is convex and has no coincident points.
     177             : //
     178           0 : bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize,
     179             :                           std::function<SkScalar(int index)> insetDistanceFunc,
     180             :                           SkTDArray<SkPoint>* insetPolygon) {
     181           0 :     if (inputPolygonSize < 3) {
     182           0 :         return false;
     183             :     }
     184             : 
     185           0 :     int winding = get_winding(inputPolygonVerts, inputPolygonSize);
     186           0 :     if (0 == winding) {
     187           0 :         return false;
     188             :     }
     189             : 
     190             :     // set up
     191             :     struct EdgeData {
     192             :         InsetSegment fInset;
     193             :         SkPoint      fIntersection;
     194             :         SkScalar     fTValue;
     195             :         bool         fValid;
     196             :     };
     197             : 
     198           0 :     SkAutoSTMalloc<64, EdgeData> edgeData(inputPolygonSize);
     199           0 :     for (int i = 0; i < inputPolygonSize; ++i) {
     200           0 :         int j = (i + 1) % inputPolygonSize;
     201           0 :         SkOffsetSegment(inputPolygonVerts[i], inputPolygonVerts[j],
     202             :                         insetDistanceFunc(i), insetDistanceFunc(j),
     203             :                         winding,
     204           0 :                         &edgeData[i].fInset.fP0, &edgeData[i].fInset.fP1);
     205           0 :         edgeData[i].fIntersection = edgeData[i].fInset.fP0;
     206           0 :         edgeData[i].fTValue = SK_ScalarMin;
     207           0 :         edgeData[i].fValid = true;
     208             :     }
     209             : 
     210           0 :     int prevIndex = inputPolygonSize - 1;
     211           0 :     int currIndex = 0;
     212           0 :     int insetVertexCount = inputPolygonSize;
     213           0 :     while (prevIndex != currIndex) {
     214           0 :         if (!edgeData[prevIndex].fValid) {
     215           0 :             prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize;
     216           0 :             continue;
     217             :         }
     218             : 
     219             :         SkScalar s, t;
     220             :         SkPoint intersection;
     221           0 :         if (compute_intersection(edgeData[prevIndex].fInset, edgeData[currIndex].fInset,
     222             :                                  &intersection, &s, &t)) {
     223             :             // if new intersection is further back on previous inset from the prior intersection
     224           0 :             if (s < edgeData[prevIndex].fTValue) {
     225             :                 // no point in considering this one again
     226           0 :                 edgeData[prevIndex].fValid = false;
     227           0 :                 --insetVertexCount;
     228             :                 // go back one segment
     229           0 :                 prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize;
     230             :             // we've already considered this intersection, we're done
     231           0 :             } else if (edgeData[currIndex].fTValue > SK_ScalarMin &&
     232           0 :                        intersection.equalsWithinTolerance(edgeData[currIndex].fIntersection,
     233             :                                                           1.0e-6f)) {
     234           0 :                 break;
     235             :             } else {
     236             :                 // add intersection
     237           0 :                 edgeData[currIndex].fIntersection = intersection;
     238           0 :                 edgeData[currIndex].fTValue = t;
     239             : 
     240             :                 // go to next segment
     241           0 :                 prevIndex = currIndex;
     242           0 :                 currIndex = (currIndex + 1) % inputPolygonSize;
     243             :             }
     244             :         } else {
     245             :             // if prev to right side of curr
     246           0 :             int side = winding*compute_side(edgeData[currIndex].fInset.fP0,
     247           0 :                                             edgeData[currIndex].fInset.fP1,
     248           0 :                                             edgeData[prevIndex].fInset.fP1);
     249           0 :             if (side < 0 && side == winding*compute_side(edgeData[currIndex].fInset.fP0,
     250           0 :                                                          edgeData[currIndex].fInset.fP1,
     251           0 :                                                          edgeData[prevIndex].fInset.fP0)) {
     252             :                 // no point in considering this one again
     253           0 :                 edgeData[prevIndex].fValid = false;
     254           0 :                 --insetVertexCount;
     255             :                 // go back one segment
     256           0 :                 prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize;
     257             :             } else {
     258             :                 // move to next segment
     259           0 :                 edgeData[currIndex].fValid = false;
     260           0 :                 --insetVertexCount;
     261           0 :                 currIndex = (currIndex + 1) % inputPolygonSize;
     262             :             }
     263             :         }
     264             :     }
     265             : 
     266             :     // store all the valid intersections that aren't nearly coincident
     267             :     // TODO: look at the main algorithm and see if we can detect these better
     268             :     static constexpr SkScalar kCleanupTolerance = 0.01f;
     269             : 
     270           0 :     insetPolygon->reset();
     271           0 :     insetPolygon->setReserve(insetVertexCount);
     272           0 :     currIndex = -1;
     273           0 :     for (int i = 0; i < inputPolygonSize; ++i) {
     274           0 :         if (edgeData[i].fValid && (currIndex == -1 ||
     275           0 :             !edgeData[i].fIntersection.equalsWithinTolerance((*insetPolygon)[currIndex],
     276             :                                                              kCleanupTolerance))) {
     277           0 :             *insetPolygon->push() = edgeData[i].fIntersection;
     278           0 :             currIndex++;
     279             :         }
     280             :     }
     281             :     // make sure the first and last points aren't coincident
     282           0 :     if (currIndex >= 1 &&
     283           0 :         (*insetPolygon)[0].equalsWithinTolerance((*insetPolygon)[currIndex],
     284             :                                                  kCleanupTolerance)) {
     285           0 :         insetPolygon->pop();
     286             :     }
     287           0 :     SkASSERT(is_convex(*insetPolygon));
     288             : 
     289           0 :     return (insetPolygon->count() >= 3);
     290             : }

Generated by: LCOV version 1.13