1/*
2 * Copyright 2006 The Android Open Source Project
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 "SkStrokerPriv.h"
9#include "SkGeometry.h"
10#include "SkPath.h"
11#include "SkPointPriv.h"
12
13static void ButtCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
14                       const SkPoint& stop, SkPath*) {
15    path->lineTo(stop.fX, stop.fY);
16}
17
18static void RoundCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
19                        const SkPoint& stop, SkPath*) {
20    SkVector parallel;
21    SkPointPriv::RotateCW(normal, &parallel);
22
23    SkPoint projectedCenter = pivot + parallel;
24
25    path->conicTo(projectedCenter + normal, projectedCenter, SK_ScalarRoot2Over2);
26    path->conicTo(projectedCenter - normal, stop, SK_ScalarRoot2Over2);
27}
28
29static void SquareCapper(SkPath* path, const SkPoint& pivot, const SkVector& normal,
30                         const SkPoint& stop, SkPath* otherPath) {
31    SkVector parallel;
32    SkPointPriv::RotateCW(normal, &parallel);
33
34    if (otherPath) {
35        path->setLastPt(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY);
36        path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY);
37    } else {
38        path->lineTo(pivot.fX + normal.fX + parallel.fX, pivot.fY + normal.fY + parallel.fY);
39        path->lineTo(pivot.fX - normal.fX + parallel.fX, pivot.fY - normal.fY + parallel.fY);
40        path->lineTo(stop.fX, stop.fY);
41    }
42}
43
44/////////////////////////////////////////////////////////////////////////////
45
46static bool is_clockwise(const SkVector& before, const SkVector& after) {
47    return before.fX * after.fY > before.fY * after.fX;
48}
49
50enum AngleType {
51    kNearly180_AngleType,
52    kSharp_AngleType,
53    kShallow_AngleType,
54    kNearlyLine_AngleType
55};
56
57static AngleType Dot2AngleType(SkScalar dot) {
58// need more precise fixed normalization
59//  SkASSERT(SkScalarAbs(dot) <= SK_Scalar1 + SK_ScalarNearlyZero);
60
61    if (dot >= 0) { // shallow or line
62        return SkScalarNearlyZero(SK_Scalar1 - dot) ? kNearlyLine_AngleType : kShallow_AngleType;
63    } else {           // sharp or 180
64        return SkScalarNearlyZero(SK_Scalar1 + dot) ? kNearly180_AngleType : kSharp_AngleType;
65    }
66}
67
68static void HandleInnerJoin(SkPath* inner, const SkPoint& pivot, const SkVector& after) {
69#if 1
70    /*  In the degenerate case that the stroke radius is larger than our segments
71        just connecting the two inner segments may "show through" as a funny
72        diagonal. To pseudo-fix this, we go through the pivot point. This adds
73        an extra point/edge, but I can't see a cheap way to know when this is
74        not needed :(
75    */
76    inner->lineTo(pivot.fX, pivot.fY);
77#endif
78
79    inner->lineTo(pivot.fX - after.fX, pivot.fY - after.fY);
80}
81
82static void BluntJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
83                        const SkPoint& pivot, const SkVector& afterUnitNormal,
84                        SkScalar radius, SkScalar invMiterLimit, bool, bool) {
85    SkVector    after;
86    afterUnitNormal.scale(radius, &after);
87
88    if (!is_clockwise(beforeUnitNormal, afterUnitNormal)) {
89        SkTSwap<SkPath*>(outer, inner);
90        after.negate();
91    }
92
93    outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY);
94    HandleInnerJoin(inner, pivot, after);
95}
96
97static void RoundJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
98                        const SkPoint& pivot, const SkVector& afterUnitNormal,
99                        SkScalar radius, SkScalar invMiterLimit, bool, bool) {
100    SkScalar    dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal);
101    AngleType   angleType = Dot2AngleType(dotProd);
102
103    if (angleType == kNearlyLine_AngleType)
104        return;
105
106    SkVector            before = beforeUnitNormal;
107    SkVector            after = afterUnitNormal;
108    SkRotationDirection dir = kCW_SkRotationDirection;
109
110    if (!is_clockwise(before, after)) {
111        SkTSwap<SkPath*>(outer, inner);
112        before.negate();
113        after.negate();
114        dir = kCCW_SkRotationDirection;
115    }
116
117    SkMatrix    matrix;
118    matrix.setScale(radius, radius);
119    matrix.postTranslate(pivot.fX, pivot.fY);
120    SkConic conics[SkConic::kMaxConicsForArc];
121    int count = SkConic::BuildUnitArc(before, after, dir, &matrix, conics);
122    if (count > 0) {
123        for (int i = 0; i < count; ++i) {
124            outer->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
125        }
126        after.scale(radius);
127        HandleInnerJoin(inner, pivot, after);
128    }
129}
130
131#define kOneOverSqrt2   (0.707106781f)
132
133static void MiterJoiner(SkPath* outer, SkPath* inner, const SkVector& beforeUnitNormal,
134                        const SkPoint& pivot, const SkVector& afterUnitNormal,
135                        SkScalar radius, SkScalar invMiterLimit,
136                        bool prevIsLine, bool currIsLine) {
137    // negate the dot since we're using normals instead of tangents
138    SkScalar    dotProd = SkPoint::DotProduct(beforeUnitNormal, afterUnitNormal);
139    AngleType   angleType = Dot2AngleType(dotProd);
140    SkVector    before = beforeUnitNormal;
141    SkVector    after = afterUnitNormal;
142    SkVector    mid;
143    SkScalar    sinHalfAngle;
144    bool        ccw;
145
146    if (angleType == kNearlyLine_AngleType) {
147        return;
148    }
149    if (angleType == kNearly180_AngleType) {
150        currIsLine = false;
151        goto DO_BLUNT;
152    }
153
154    ccw = !is_clockwise(before, after);
155    if (ccw) {
156        SkTSwap<SkPath*>(outer, inner);
157        before.negate();
158        after.negate();
159    }
160
161    /*  Before we enter the world of square-roots and divides,
162        check if we're trying to join an upright right angle
163        (common case for stroking rectangles). If so, special case
164        that (for speed an accuracy).
165        Note: we only need to check one normal if dot==0
166    */
167    if (0 == dotProd && invMiterLimit <= kOneOverSqrt2) {
168        mid = (before + after) * radius;
169        goto DO_MITER;
170    }
171
172    /*  midLength = radius / sinHalfAngle
173        if (midLength > miterLimit * radius) abort
174        if (radius / sinHalf > miterLimit * radius) abort
175        if (1 / sinHalf > miterLimit) abort
176        if (1 / miterLimit > sinHalf) abort
177        My dotProd is opposite sign, since it is built from normals and not tangents
178        hence 1 + dot instead of 1 - dot in the formula
179    */
180    sinHalfAngle = SkScalarSqrt(SkScalarHalf(SK_Scalar1 + dotProd));
181    if (sinHalfAngle < invMiterLimit) {
182        currIsLine = false;
183        goto DO_BLUNT;
184    }
185
186    // choose the most accurate way to form the initial mid-vector
187    if (angleType == kSharp_AngleType) {
188        mid.set(after.fY - before.fY, before.fX - after.fX);
189        if (ccw) {
190            mid.negate();
191        }
192    } else {
193        mid.set(before.fX + after.fX, before.fY + after.fY);
194    }
195
196    mid.setLength(radius / sinHalfAngle);
197DO_MITER:
198    if (prevIsLine) {
199        outer->setLastPt(pivot.fX + mid.fX, pivot.fY + mid.fY);
200    } else {
201        outer->lineTo(pivot.fX + mid.fX, pivot.fY + mid.fY);
202    }
203
204DO_BLUNT:
205    after.scale(radius);
206    if (!currIsLine) {
207        outer->lineTo(pivot.fX + after.fX, pivot.fY + after.fY);
208    }
209    HandleInnerJoin(inner, pivot, after);
210}
211
212/////////////////////////////////////////////////////////////////////////////
213
214SkStrokerPriv::CapProc SkStrokerPriv::CapFactory(SkPaint::Cap cap) {
215    const SkStrokerPriv::CapProc gCappers[] = {
216        ButtCapper, RoundCapper, SquareCapper
217    };
218
219    SkASSERT((unsigned)cap < SkPaint::kCapCount);
220    return gCappers[cap];
221}
222
223SkStrokerPriv::JoinProc SkStrokerPriv::JoinFactory(SkPaint::Join join) {
224    const SkStrokerPriv::JoinProc gJoiners[] = {
225        MiterJoiner, RoundJoiner, BluntJoiner
226    };
227
228    SkASSERT((unsigned)join < SkPaint::kJoinCount);
229    return gJoiners[join];
230}
231