Skip to content

Commit 99f3c85

Browse files
authored
Merge pull request #10 from ToddGeorgeKelly/master
Curved Regular Polygons
2 parents 432588a + aa8d4be commit 99f3c85

File tree

8 files changed

+158
-49
lines changed

8 files changed

+158
-49
lines changed

Sources/Shapes/RegularPolygons/Decagon.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,31 @@ import SwiftUI
22

33
public struct Decagon: InsettableShape {
44
let inset: CGFloat
5+
let radius: CGFloat
56

67
public func inset(by amount: CGFloat) -> Decagon {
7-
Decagon(inset: self.inset + amount)
8+
Decagon(inset: self.inset + amount, radius: radius)
89
}
910

1011
public func path(in rect: CGRect) -> Path {
11-
Path.regularPolygon(sides: 10, in: rect, inset: inset)
12+
Path.regularPolygon(sides: 10, in: rect, inset: inset, radius: radius)
1213
}
1314

14-
public init() {
15-
inset = 0
16-
}
15+
public init() {
16+
inset = 0
17+
radius = 0
18+
}
19+
20+
public init(radius: CGFloat) {
21+
self.inset = 0
22+
self.radius = radius
23+
}
1724
}
1825

1926
extension Decagon {
20-
init(inset: CGFloat) {
27+
init(inset: CGFloat, radius: CGFloat) {
2128
self.inset = inset
29+
self.radius = radius
2230
}
2331
}
2432

Sources/Shapes/RegularPolygons/Heptagon.swift

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,32 @@ import SwiftUI
22

33
public struct Heptagon: InsettableShape {
44
let inset: CGFloat
5+
let radius: CGFloat
56

67
public func inset(by amount: CGFloat) -> Heptagon {
7-
Heptagon(inset: self.inset + amount)
8+
Heptagon(inset: self.inset + amount, radius: radius)
89
}
910

1011
public func path(in rect: CGRect) -> Path {
11-
Path.regularPolygon(sides: 7, in: rect, inset: inset)
12+
Path.regularPolygon(sides: 7, in: rect, inset: inset, radius: radius)
1213
}
1314

14-
public init() {
15-
inset = 0
16-
}
15+
public init() {
16+
inset = 0
17+
radius = 0
18+
}
19+
20+
public init(radius: CGFloat) {
21+
self.inset = 0
22+
self.radius = radius
23+
}
1724
}
1825

1926
extension Heptagon {
20-
init(inset: CGFloat) {
21-
self.inset = inset
22-
}
27+
init(inset: CGFloat, radius: CGFloat) {
28+
self.inset = inset
29+
self.radius = radius
30+
}
2331
}
2432

2533
struct Heptagon_Previews: PreviewProvider {

Sources/Shapes/RegularPolygons/Hexagon.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,31 @@ import SwiftUI
22

33
public struct Hexagon: InsettableShape {
44
let inset: CGFloat
5+
let radius: CGFloat
56

67
public func inset(by amount: CGFloat) -> Hexagon {
7-
Hexagon(inset: self.inset + amount)
8+
Hexagon(inset: self.inset + amount, radius: radius)
89
}
910

1011
public func path(in rect: CGRect) -> Path {
11-
Path.regularPolygon(sides: 6, in: rect, inset: inset)
12+
Path.regularPolygon(sides: 6, in: rect, inset: inset, radius: radius)
1213
}
1314

14-
public init() {
15-
inset = 0
16-
}
15+
public init() {
16+
inset = 0
17+
radius = 0
18+
}
19+
20+
public init(radius: CGFloat) {
21+
self.inset = 0
22+
self.radius = radius
23+
}
1724
}
1825

1926
extension Hexagon {
20-
init(inset: CGFloat) {
27+
init(inset: CGFloat, radius: CGFloat) {
2128
self.inset = inset
29+
self.radius = radius
2230
}
2331
}
2432

Sources/Shapes/RegularPolygons/Nonagon.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,31 @@ import SwiftUI
22

33
public struct Nonagon: InsettableShape {
44
let inset: CGFloat
5+
let radius: CGFloat
56

67
public func inset(by amount: CGFloat) -> Nonagon {
7-
Nonagon(inset: self.inset + amount)
8+
Nonagon(inset: self.inset + amount, radius: radius)
89
}
910

1011
public func path(in rect: CGRect) -> Path {
11-
Path.regularPolygon(sides: 9, in: rect, inset: inset)
12+
Path.regularPolygon(sides: 9, in: rect, inset: inset, radius: radius)
1213
}
1314

14-
public init() {
15-
inset = 0
16-
}
15+
public init() {
16+
inset = 0
17+
radius = 0
18+
}
19+
20+
public init(radius: CGFloat) {
21+
self.inset = 0
22+
self.radius = radius
23+
}
1724
}
1825

1926
extension Nonagon {
20-
init(inset: CGFloat) {
27+
init(inset: CGFloat, radius: CGFloat) {
2128
self.inset = inset
29+
self.radius = radius
2230
}
2331
}
2432

Sources/Shapes/RegularPolygons/Octagon.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,31 @@ import SwiftUI
22

33
public struct Octagon: InsettableShape {
44
let inset: CGFloat
5+
let radius: CGFloat
56

67
public func inset(by amount: CGFloat) -> Octagon {
7-
Octagon(inset: self.inset + amount)
8+
Octagon(inset: self.inset + amount, radius: radius)
89
}
910

1011
public func path(in rect: CGRect) -> Path {
11-
Path.regularPolygon(sides: 8, in: rect, inset: inset)
12+
Path.regularPolygon(sides: 8, in: rect, inset: inset, radius: radius)
1213
}
1314

1415
public init() {
1516
inset = 0
17+
radius = 0
1618
}
19+
20+
public init(radius: CGFloat) {
21+
self.inset = 0
22+
self.radius = radius
23+
}
1724
}
1825

1926
extension Octagon {
20-
init(inset: CGFloat) {
27+
init(inset: CGFloat, radius: CGFloat) {
2128
self.inset = inset
29+
self.radius = radius
2230
}
2331
}
2432

Sources/Shapes/RegularPolygons/Pentagon.swift

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,32 @@ import SwiftUI
22

33
public struct Pentagon: InsettableShape {
44
let inset: CGFloat
5+
let radius: CGFloat
56

67
public func inset(by amount: CGFloat) -> Pentagon {
7-
Pentagon(inset: self.inset + amount)
8+
Pentagon(inset: self.inset + amount, radius: radius)
89
}
910

1011
public func path(in rect: CGRect) -> Path {
11-
Path.regularPolygon(sides: 5, in: rect, inset: inset)
12+
Path.regularPolygon(sides: 5, in: rect, inset: inset, radius: radius)
1213
}
1314

14-
public init() {
15-
inset = 0
16-
}
15+
public init() {
16+
inset = 0
17+
radius = 0
18+
}
19+
20+
public init(radius: CGFloat) {
21+
self.inset = 0
22+
self.radius = radius
23+
}
1724
}
1825

1926
extension Pentagon {
20-
init(inset: CGFloat) {
21-
self.inset = inset
22-
}
27+
init(inset: CGFloat, radius: CGFloat) {
28+
self.inset = inset
29+
self.radius = radius
30+
}
2331
}
2432

2533
struct Pentagon_Previews: PreviewProvider {

Sources/Shapes/RegularPolygons/RegularPolygon.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,52 @@ import SwiftUI
33
public struct RegularPolygon: InsettableShape {
44
let sides: Int
55
let inset: CGFloat
6+
let radius: CGFloat
67

78
public func inset(by amount: CGFloat) -> RegularPolygon {
8-
RegularPolygon(sides: self.sides, inset: self.inset + amount)
9+
RegularPolygon(sides: self.sides, inset: self.inset + amount, radius: radius)
910
}
1011

1112
public func path(in rect: CGRect) -> Path {
12-
Path.regularPolygon(sides: self.sides, in: rect, inset: inset)
13+
Path.regularPolygon(sides: self.sides, in: rect, inset: inset, radius: radius)
1314
}
1415

15-
public init(sides: Int) {
16+
public init(sides: Int, radius: CGFloat = 0) {
1617
self.sides = sides
1718
self.inset = 0
19+
self.radius = radius
1820
}
1921

20-
public init(sides: Double) {
22+
public init(sides: Double, radius: CGFloat = 0) {
2123
self.sides = Int(sides.rounded(.down))
2224
self.inset = 0
25+
self.radius = radius
2326
}
2427
}
2528

2629
extension RegularPolygon {
27-
init(sides: Int, inset: CGFloat) {
30+
init(sides: Int, inset: CGFloat, radius: CGFloat = 0) {
2831
self.sides = sides
2932
self.inset = inset
33+
self.radius = radius
3034
}
3135
}
3236

3337
struct RegularPolygon_Previews: PreviewProvider {
3438
static var previews: some View {
3539
Group {
36-
RegularPolygon(sides: 4)
40+
RegularPolygon(sides: 4, radius: 5)
3741
.strokeBorder(lineWidth: 20)
3842
.foregroundColor(.blue)
3943

40-
Pentagon()
44+
Pentagon(radius: 5)
4145
.strokeBorder(lineWidth: 20)
4246
.foregroundColor(.yellow)
4347

4448
Hexagon()
4549
.foregroundColor(.orange)
4650

47-
Heptagon()
51+
Heptagon(radius: 5)
4852
.foregroundColor(.blue)
4953

5054
Octagon()

Sources/Shapes/RegularPolygons/RegularPolygonPath.swift

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,80 @@
11
import SwiftUI
22

33
extension Path {
4-
static func regularPolygon(sides: Int, in rect: CGRect, inset: CGFloat = 0) -> Path {
4+
static func regularPolygon(sides: Int, in rect: CGRect, inset: CGFloat = 0, radius: CGFloat = 0) -> Path {
55
let width = rect.size.width - inset * 2
66
let height = rect.size.height - inset * 2
77
let hypotenuse = Double(min(width, height)) / 2.0
88
let centerPoint = CGPoint(x: width / 2.0, y: height / 2.0)
9-
9+
var testDistance: CGFloat = .zero
10+
var usableRadius: CGFloat = .zero
11+
12+
1013
return Path { path in
1114
(0...sides).forEach { index in
1215
let angle = ((Double(index) * (360.0 / Double(sides))) - 90) * Double.pi / 180
16+
17+
//control point
1318
let point = CGPoint(
1419
x: centerPoint.x + CGFloat(cos(angle) * hypotenuse),
1520
y: centerPoint.y + CGFloat(sin(angle) * hypotenuse)
1621
)
22+
23+
//the angle from the target control point to the next control point
24+
let nextAngle = ((Double(index + 1) * (360.0 / Double(sides))) - 90) * Double.pi / 180
25+
26+
//coordinates of the next control point
27+
let nextPoint = CGPoint(
28+
x: centerPoint.x + CGFloat(cos(nextAngle) * hypotenuse),
29+
y: centerPoint.y + CGFloat(sin(nextAngle) * hypotenuse)
30+
)
31+
32+
if testDistance == .zero {
33+
//The distance between two neighboring endpoints on your polygon
34+
testDistance = sqrt(pow(( nextPoint.x - point.x ), 2) + pow(( nextPoint.y - point.y ), 2))
35+
36+
//Ensures that our 'radius' won't exceed a length of half our polygonside
37+
usableRadius = radius > testDistance / 2 ? testDistance / 2 : radius
38+
}
39+
40+
//source point
41+
let currentPoint = index == 0 ? point : path.currentPoint!
42+
43+
//distance from source point to target control point
44+
let distance = sqrt(pow(( point.x - currentPoint.x ), 2) + pow(( point.y - currentPoint.y ), 2))
45+
46+
//distance from target control point to the start of the curve we want to draw
47+
let distanceToCurveStart = index == 0 ? usableRadius : distance - usableRadius
48+
49+
//angle from current point to the target control point
50+
let angleToCurveStart = index == 0 ? 0 : atan2((point.y - currentPoint.y), (point.x - currentPoint.x))
51+
52+
//coordinates of where to start the curve
53+
let curveStartPoint = CGPoint(
54+
x: currentPoint.x + (distanceToCurveStart * CGFloat(cos(angleToCurveStart))),
55+
y: currentPoint.y + (distanceToCurveStart * CGFloat(sin(angleToCurveStart)))
56+
)
57+
58+
//angle from current control point to next control point
59+
let angleToCurveEnd = atan2((nextPoint.y - point.y), (nextPoint.x - point.x))
60+
61+
//coordinates of where the curve shuold end
62+
let curveEndPoint = CGPoint(
63+
x: point.x + (usableRadius * CGFloat(cos(angleToCurveEnd))),
64+
y: point.y + (usableRadius * CGFloat(sin(angleToCurveEnd)))
65+
)
66+
1767
if index == 0 {
18-
path.move(to: point)
68+
let altStartPoint = CGPoint(
69+
x: point.x + (usableRadius * CGFloat(cos(angleToCurveEnd))),
70+
y: point.y + (usableRadius * CGFloat(sin(angleToCurveEnd)))
71+
)
72+
path.move(to: radius == 0 ? point : altStartPoint)
1973
} else {
20-
path.addLine(to: point)
74+
path.addLine(to: radius > 0 ? curveStartPoint : point)
75+
if radius > 0 {
76+
path.addQuadCurve(to: curveEndPoint, control: point)
77+
}
2178
}
2279
}
2380
path.closeSubpath()

0 commit comments

Comments
 (0)