1/*
2 * Copyright 2021 Google LLC
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#ifndef skgpu_geom_Shape_DEFINED
9#define skgpu_geom_Shape_DEFINED
10
11#include "include/core/SkM44.h"
12#include "include/core/SkPath.h"
13#include "include/core/SkRRect.h"
14#include "include/core/SkRect.h"
15
16#include "experimental/graphite/src/geom/Rect.h"
17
18#include <array>
19
20namespace skgpu {
21
22/**
23 * Shape is effectively a std::variant over different geometric shapes, with the most complex
24 * being an SkPath. It provides a consistent way to query geometric properties, such as convexity,
25 * point containment, or iteration.
26 */
27class Shape {
28public:
29    enum class Type : uint8_t {
30        kEmpty, kLine, kRect, kRRect, kPath
31    };
32    inline static constexpr int kTypeCount = static_cast<int>(Type::kPath) + 1;
33
34    Shape() {}
35    Shape(const Shape& shape)            { *this = shape; }
36    Shape(Shape&&) = delete;
37
38    Shape(SkPoint p0, SkPoint p1)        { this->setLine(p0, p1); }
39    Shape(SkV2 p0, SkV2 p1)              { this->setLine(p0, p1); }
40    Shape(float2 p0, float2 p1)          { this->setLine(p0, p1); }
41    explicit Shape(const Rect& rect)     { this->setRect(rect);   }
42    explicit Shape(const SkRect& rect)   { this->setRect(rect);   }
43    explicit Shape(const SkRRect& rrect) { this->setRRect(rrect); }
44    explicit Shape(const SkPath& path)   { this->setPath(path);   }
45
46    ~Shape() { this->reset(); }
47
48    // NOTE: None of the geometry types benefit from move semantics, so we don't bother
49    // defining a move assignment operator for Shape.
50    Shape& operator=(Shape&&) = delete;
51    Shape& operator=(const Shape&);
52
53    // Return the type of the data last stored in the Shape, which does not incorporate any possible
54    // simplifications that could be applied to it (e.g. a degenerate round rect with 0 radius
55    // corners is kRRect and not kRect).
56    Type type() const { return fType; }
57
58    bool isEmpty() const { return fType == Type::kEmpty; }
59    bool isLine()  const { return fType == Type::kLine;  }
60    bool isRect()  const { return fType == Type::kRect;  }
61    bool isRRect() const { return fType == Type::kRRect; }
62    bool isPath()  const { return fType == Type::kPath;  }
63
64    bool inverted() const {
65        SkASSERT(fType != Type::kPath || fInverted == fPath.isInverseFillType());
66        return fInverted;
67    }
68
69    void setInverted(bool inverted) {
70        if (fType == Type::kPath && inverted != fPath.isInverseFillType()) {
71            fPath.toggleInverseFillType();
72        }
73        fInverted = inverted;
74    }
75
76    SkPathFillType fillType() const {
77        if (fType == Type::kPath) {
78            return fPath.getFillType(); // already incorporates invertedness
79        } else {
80            return fInverted ? SkPathFillType::kInverseEvenOdd : SkPathFillType::kEvenOdd;
81        }
82    }
83
84    // True if the given bounding box is completely inside the shape, if it's conservatively treated
85    // as a filled, closed shape.
86    bool conservativeContains(const Rect& rect) const;
87    bool conservativeContains(float2 point) const;
88
89    // True if the underlying geometry represents a closed shape, without the need for an
90    // implicit close.
91    bool closed() const;
92
93    // True if the underlying shape is known to be convex, assuming no other styles. If 'simpleFill'
94    // is true, it is assumed the contours will be implicitly closed when drawn or used.
95    bool convex(bool simpleFill = true) const;
96
97    // The bounding box of the shape.
98    Rect bounds() const;
99
100    // Convert the shape into a path that describes the same geometry.
101    SkPath asPath() const;
102
103    // Access the actual geometric description of the shape. May only access the appropriate type
104    // based on what was last set.
105    float2         p0()    const { SkASSERT(this->isLine());  return fRect.topLeft();  }
106    float2         p1()    const { SkASSERT(this->isLine());  return fRect.botRight(); }
107    const Rect&    rect()  const { SkASSERT(this->isRect());  return fRect;            }
108    const SkRRect& rrect() const { SkASSERT(this->isRRect()); return fRRect;           }
109    const SkPath&  path()  const { SkASSERT(this->isPath());  return fPath;            }
110
111    // Update the geometry stored in the Shape and update its associated type to match. This
112    // performs no simplification, so calling setRRect() with a round rect that has isRect() return
113    // true will still be considered an rrect by Shape.
114    //
115    // These reset inversion to the default for the geometric type.
116    void setLine(SkPoint p0, SkPoint p1) {
117        this->setLine(float2{p0.fX, p0.fY}, float2{p1.fX, p1.fY});
118    }
119    void setLine(SkV2 p0, SkV2 p1) {
120        this->setLine(float2{p0.x, p0.y}, float2{p1.x, p1.y});
121    }
122    void setLine(float2 p0, float2 p1) {
123        this->setType(Type::kLine);
124        fRect = Rect(p0, p1);
125        fInverted = false;
126    }
127    void setRect(const SkRect& rect) { this->setRect(Rect(rect)); }
128    void setRect(const Rect& rect) {
129        this->setType(Type::kRect);
130        fRect = rect;
131        fInverted = false;
132    }
133    void setRRect(const SkRRect& rrect) {
134        this->setType(Type::kRRect);
135        fRRect = rrect;
136        fInverted = false;
137    }
138    void setPath(const SkPath& path) {
139        if (fType == Type::kPath) {
140            // Assign directly
141            fPath = path;
142        } else {
143            // In-place initialize
144            this->setType(Type::kPath);
145            new (&fPath) SkPath(path);
146        }
147        fInverted = path.isInverseFillType();
148    }
149
150    void reset() {
151        this->setType(Type::kEmpty);
152        fInverted = false;
153    }
154
155private:
156    void setType(Type type) {
157        if (this->isPath() && type != Type::kPath) {
158            fPath.~SkPath();
159        }
160        fType = type;
161    }
162
163    union {
164        Rect    fRect; // p0 = top-left, p1 = bot-right if type is kLine (may be unsorted)
165        SkRRect fRRect;
166        SkPath  fPath;
167    };
168
169    Type    fType     = Type::kEmpty;
170    bool    fInverted = false;
171};
172
173} // namespace skgpu
174
175#endif // skgpu_geom_Shape_DEFINED
176