Skip to content

Commit 941c5ff

Browse files
committed
My math is wrong...
1 parent 84a3a2e commit 941c5ff

File tree

3 files changed

+140
-79
lines changed

3 files changed

+140
-79
lines changed

Sources/SwiftCrossUI/Path.swift

Lines changed: 114 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Foundation // for sinf and cosf
1+
import Foundation // for sin and cos
22

33
public enum StrokeCap {
44
/// The stroke ends square exactly at the last point.
@@ -12,15 +12,15 @@ public enum StrokeCap {
1212
public enum StrokeJoin {
1313
/// Corners are sharp, unless they are longer than `limit` times half the stroke width,
1414
/// in which case they are beveled.
15-
case miter(limit: Float)
15+
case miter(limit: Double)
1616
/// Corners are rounded.
1717
case round
1818
/// Corners are beveled.
1919
case bevel
2020
}
2121

2222
public struct StrokeStyle {
23-
public var width: Float = 1.0
23+
public var width: Double = 1.0
2424
public var cap: StrokeCap = .butt
2525
public var join: StrokeJoin = .miter(limit: 10.0)
2626

@@ -39,45 +39,43 @@ public enum FillRule {
3939

4040
/// A type representing an affine transformation on a 2-D point.
4141
///
42-
/// Performing an affine transform consists of translating by ``translation`` followed by
43-
/// multiplying by ``linearTransform``.
44-
public struct AffineTransform: Equatable {
42+
/// Performing an affine transform consists of multiplying the matrix ``linearTransform``
43+
/// by the point as a column vector, then adding ``translation``.
44+
public struct AffineTransform: Equatable, CustomDebugStringConvertible {
4545
/// The linear transformation. This is a 2x2 matrix stored in row-major order.
4646
///
4747
/// The four properties (`x`, `y`, `z`, `w`) correspond to the 2x2 matrix as follows:
4848
/// ```
4949
/// [ x y ]
5050
/// [ z w ]
5151
/// ```
52-
public var linearTransform: SIMD4<Float>
52+
public var linearTransform: SIMD4<Double>
5353
/// The translation applied after the linear transformation.
54-
public var translation: SIMD2<Float>
54+
public var translation: SIMD2<Double>
5555

56-
public init(linearTransform: SIMD4<Float>, translation: SIMD2<Float>) {
56+
public init(linearTransform: SIMD4<Double>, translation: SIMD2<Double>) {
5757
self.linearTransform = linearTransform
5858
self.translation = translation
5959
}
6060

61-
public static func translate(x: Float, y: Float) -> AffineTransform {
61+
public static func translation(x: Double, y: Double) -> AffineTransform {
6262
AffineTransform(
6363
linearTransform: SIMD4(x: 1.0, y: 0.0, z: 0.0, w: 1.0),
6464
translation: SIMD2(x: x, y: y)
6565
)
6666
}
6767

68-
public static func scale(by factor: Float) -> AffineTransform {
68+
public static func scaling(by factor: Double) -> AffineTransform {
6969
AffineTransform(
7070
linearTransform: SIMD4(x: factor, y: 0.0, z: 0.0, w: factor),
7171
translation: .zero
7272
)
7373
}
7474

75-
public static func rotate(
76-
radians: Float,
77-
center: SIMD2<Float> = .zero
78-
) -> AffineTransform {
79-
let sine = sinf(radians)
80-
let cosine = cosf(radians)
75+
public static func rotation(radians: Double, center: SIMD2<Double>) -> AffineTransform {
76+
// Making the sine negative so that positive rotations are clockwise
77+
let sine = sin(-radians)
78+
let cosine = cos(radians)
8179
return AffineTransform(
8280
linearTransform: SIMD4(x: cosine, y: -sine, z: sine, w: cosine),
8381
translation: SIMD2(
@@ -87,53 +85,118 @@ public struct AffineTransform: Equatable {
8785
)
8886
}
8987

90-
public static func rotate(
91-
degrees: Float,
92-
center: SIMD2<Float> = .zero
93-
) -> AffineTransform {
94-
rotate(radians: degrees * (.pi / 180.0), center: center)
88+
public static func rotation(degrees: Double, center: SIMD2<Double>) -> AffineTransform {
89+
rotation(radians: degrees * (.pi / 180.0), center: center)
9590
}
9691

9792
public static let identity = AffineTransform(
9893
linearTransform: SIMD4(x: 1.0, y: 0.0, z: 0.0, w: 1.0),
9994
translation: .zero
10095
)
96+
97+
public func inverted() -> AffineTransform? {
98+
let determinant = linearTransform.x * linearTransform.w - linearTransform.y * linearTransform.z
99+
if determinant == 0.0 {
100+
return nil
101+
}
102+
103+
return AffineTransform(
104+
linearTransform: SIMD4(
105+
x: linearTransform.w,
106+
y: -linearTransform.y,
107+
z: -linearTransform.z,
108+
w: linearTransform.x
109+
) / determinant,
110+
translation: SIMD2(
111+
x: (linearTransform.y * translation.y - linearTransform.w * translation.x),
112+
y: (linearTransform.z * translation.x - linearTransform.x * translation.y)
113+
) / determinant
114+
)
115+
}
116+
117+
public func followedBy(_ other: AffineTransform) -> AffineTransform {
118+
// Composing two transformations is equivalent to forming the 3x3 matrix shown by
119+
// `debugDescription`, then multiplying `other * self` (the left matrix is applied
120+
// after the right matrix).
121+
return AffineTransform(
122+
linearTransform: SIMD4(
123+
x: other.linearTransform.x * linearTransform.x + other.linearTransform.y
124+
* linearTransform.z,
125+
y: other.linearTransform.x * linearTransform.y + other.linearTransform.y
126+
* linearTransform.w,
127+
z: other.linearTransform.z * linearTransform.x + other.linearTransform.w
128+
* linearTransform.z,
129+
w: other.linearTransform.z * linearTransform.y + other.linearTransform.w
130+
* linearTransform.w
131+
),
132+
translation: SIMD2(
133+
x: other.linearTransform.x * translation.x + other.linearTransform.y * translation.y
134+
+ other.translation.x,
135+
y: other.linearTransform.z * translation.x + other.linearTransform.w * translation.y
136+
+ other.translation.y
137+
)
138+
)
139+
}
140+
141+
public var debugDescription: String {
142+
let numberFormat = "%.5g"
143+
let a = String(format: numberFormat, linearTransform.x)
144+
let b = String(format: numberFormat, linearTransform.y)
145+
let c = String(format: numberFormat, linearTransform.z)
146+
let d = String(format: numberFormat, linearTransform.w)
147+
let tx = String(format: numberFormat, translation.x)
148+
let ty = String(format: numberFormat, translation.y)
149+
let zero = String(format: numberFormat, 0.0)
150+
let one = String(format: numberFormat, 1.0)
151+
152+
let maxLength = [a, b, c, d, tx, ty, zero, one].map(\.count).max()!
153+
154+
func pad(_ s: String) -> String {
155+
String(repeating: " ", count: maxLength - s.count) + s
156+
}
157+
158+
return """
159+
[ \(pad(a)) \(pad(b)) \(pad(tx)) ]
160+
[ \(pad(c)) \(pad(d)) \(pad(ty)) ]
161+
[ \(pad(zero)) \(pad(zero)) \(pad(one)) ]
162+
"""
163+
}
101164
}
102165

103166
public struct Path {
104167
public struct Rect: Equatable {
105-
public var origin: SIMD2<Float>
106-
public var size: SIMD2<Float>
168+
public var origin: SIMD2<Double>
169+
public var size: SIMD2<Double>
107170

108-
public init(origin: SIMD2<Float>, size: SIMD2<Float>) {
171+
public init(origin: SIMD2<Double>, size: SIMD2<Double>) {
109172
self.origin = origin
110173
self.size = size
111174
}
112175

113-
public var x: Float { origin.x }
114-
public var y: Float { origin.y }
115-
public var width: Float { size.x }
116-
public var height: Float { size.y }
176+
public var x: Double { origin.x }
177+
public var y: Double { origin.y }
178+
public var width: Double { size.x }
179+
public var height: Double { size.y }
117180

118-
public init(x: Float, y: Float, width: Float, height: Float) {
181+
public init(x: Double, y: Double, width: Double, height: Double) {
119182
origin = SIMD2(x: x, y: y)
120183
size = SIMD2(x: width, y: height)
121184
}
122185
}
123186

124187
/// The types of actions that can be performed on a path.
125188
public enum Action: Equatable {
126-
case moveTo(SIMD2<Float>)
127-
case lineTo(SIMD2<Float>)
128-
case quadCurve(control: SIMD2<Float>, end: SIMD2<Float>)
129-
case cubicCurve(control1: SIMD2<Float>, control2: SIMD2<Float>, end: SIMD2<Float>)
189+
case moveTo(SIMD2<Double>)
190+
case lineTo(SIMD2<Double>)
191+
case quadCurve(control: SIMD2<Double>, end: SIMD2<Double>)
192+
case cubicCurve(control1: SIMD2<Double>, control2: SIMD2<Double>, end: SIMD2<Double>)
130193
case rectangle(Rect)
131-
case circle(center: SIMD2<Float>, radius: Float)
194+
case circle(center: SIMD2<Double>, radius: Double)
132195
case arc(
133-
center: SIMD2<Float>,
134-
radius: Float,
135-
startAngle: Float,
136-
endAngle: Float,
196+
center: SIMD2<Double>,
197+
radius: Double,
198+
startAngle: Double,
199+
endAngle: Double,
137200
clockwise: Bool
138201
)
139202
case transform(AffineTransform)
@@ -152,28 +215,28 @@ public struct Path {
152215

153216
public init() {}
154217

155-
public consuming func move(to point: SIMD2<Float>) -> Path {
218+
public consuming func move(to point: SIMD2<Double>) -> Path {
156219
actions.append(.moveTo(point))
157220
return self
158221
}
159222

160-
public consuming func addLine(to point: SIMD2<Float>) -> Path {
223+
public consuming func addLine(to point: SIMD2<Double>) -> Path {
161224
actions.append(.lineTo(point))
162225
return self
163226
}
164227

165228
public consuming func addQuadCurve(
166-
control: SIMD2<Float>,
167-
to endPoint: SIMD2<Float>
229+
control: SIMD2<Double>,
230+
to endPoint: SIMD2<Double>
168231
) -> Path {
169232
actions.append(.quadCurve(control: control, end: endPoint))
170233
return self
171234
}
172235

173236
public consuming func addCubicCurve(
174-
control1: SIMD2<Float>,
175-
control2: SIMD2<Float>,
176-
to endPoint: SIMD2<Float>
237+
control1: SIMD2<Double>,
238+
control2: SIMD2<Double>,
239+
to endPoint: SIMD2<Double>
177240
) -> Path {
178241
actions.append(.cubicCurve(control1: control1, control2: control2, end: endPoint))
179242
return self
@@ -184,16 +247,16 @@ public struct Path {
184247
return self
185248
}
186249

187-
public consuming func addCircle(center: SIMD2<Float>, radius: Float) -> Path {
250+
public consuming func addCircle(center: SIMD2<Double>, radius: Double) -> Path {
188251
actions.append(.circle(center: center, radius: radius))
189252
return self
190253
}
191254

192255
public consuming func addArc(
193-
center: SIMD2<Float>,
194-
radius: Float,
195-
startAngle: Float,
196-
endAngle: Float,
256+
center: SIMD2<Double>,
257+
radius: Double,
258+
startAngle: Double,
259+
endAngle: Double,
197260
clockwise: Bool
198261
) -> Path {
199262
actions.append(

Sources/SwiftCrossUI/Views/Shapes/Shape.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ extension Shape {
4040
let storage = children as! ShapeStorage
4141
let size = size(fitting: proposedSize)
4242

43-
let path = path(in: Path.Rect(x: 0.0, y: 0.0, width: Float(size.x), height: Float(size.y)))
43+
let path = path(in: Path.Rect(x: 0.0, y: 0.0, width: Double(size.x), height: Double(size.y)))
4444
let pointsChanged = storage.oldPath?.actions != path.actions
4545
storage.oldPath = path
4646

Sources/UIKitBackend/UIKitBackend+Path.swift

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -38,58 +38,56 @@ extension UIKitBackend {
3838
for action in source.actions {
3939
switch action {
4040
case .moveTo(let point):
41-
path.move(to: CGPoint(x: Double(point.x), y: Double(point.y)))
41+
path.move(to: CGPoint(x: point.x, y: point.y))
4242
case .lineTo(let point):
43-
path.addLine(to: CGPoint(x: Double(point.x), y: Double(point.y)))
43+
path.addLine(to: CGPoint(x: point.x, y: point.y))
4444
case .quadCurve(let control, let end):
4545
path.addQuadCurve(
46-
to: CGPoint(x: Double(end.x), y: Double(end.y)),
47-
controlPoint: CGPoint(x: Double(control.x), y: Double(control.y)))
46+
to: CGPoint(x: end.x, y: end.y),
47+
controlPoint: CGPoint(x: control.x, y: control.y)
48+
)
4849
case .cubicCurve(let control1, let control2, let end):
4950
path.addCurve(
50-
to: CGPoint(x: Double(end.x), y: Double(end.y)),
51-
controlPoint1: CGPoint(x: Double(control1.x), y: Double(control1.y)),
52-
controlPoint2: CGPoint(x: Double(control2.x), y: Double(control2.y)))
51+
to: CGPoint(x: end.x, y: end.y),
52+
controlPoint1: CGPoint(x: control1.x, y: control1.y),
53+
controlPoint2: CGPoint(x: control2.x, y: control2.y)
54+
)
5355
case .rectangle(let rect):
5456
let cgPath: CGMutablePath = path.cgPath.mutableCopy()!
5557
cgPath.addRect(
56-
CGRect(
57-
x: CGFloat(rect.x),
58-
y: CGFloat(rect.y),
59-
width: CGFloat(rect.width),
60-
height: CGFloat(rect.height)
61-
)
58+
CGRect(x: rect.x, y: rect.y, width: rect.width, height: rect.height)
6259
)
6360
path.cgPath = cgPath
6461
case .circle(let center, let radius):
6562
let cgPath: CGMutablePath = path.cgPath.mutableCopy()!
6663
cgPath.addEllipse(
6764
in: CGRect(
68-
x: CGFloat(center.x - radius),
69-
y: CGFloat(center.y - radius),
70-
width: CGFloat(radius * 2.0),
71-
height: CGFloat(radius * 2.0)
65+
x: center.x - radius,
66+
y: center.y - radius,
67+
width: radius * 2.0,
68+
height: radius * 2.0
7269
)
7370
)
7471
path.cgPath = cgPath
7572
case .arc(let center, let radius, let startAngle, let endAngle, let clockwise):
7673
path.addArc(
77-
withCenter: CGPoint(x: Double(center.x), y: Double(center.y)),
74+
withCenter: CGPoint(x: center.x, y: center.y),
7875
radius: CGFloat(radius),
7976
startAngle: CGFloat(startAngle),
8077
endAngle: CGFloat(endAngle),
8178
clockwise: clockwise
8279
)
8380
case .transform(let transform):
84-
let cgAT = CGAffineTransform(
85-
a: Double(transform.linearTransform.x),
86-
b: Double(transform.linearTransform.y),
87-
c: Double(transform.linearTransform.z),
88-
d: Double(transform.linearTransform.w),
89-
tx: Double(transform.translation.x),
90-
ty: Double(transform.translation.y)
81+
path.apply(
82+
CGAffineTransform(
83+
a: transform.linearTransform.x,
84+
b: transform.linearTransform.y,
85+
c: transform.linearTransform.z,
86+
d: transform.linearTransform.w,
87+
tx: transform.translation.x,
88+
ty: transform.translation.y
89+
)
9190
)
92-
path.apply(cgAT)
9391
}
9492
}
9593
}
@@ -101,6 +99,6 @@ extension UIKitBackend {
10199
maskLayer.path = path.cgPath
102100
maskLayer.fillRule = path.usesEvenOddFillRule ? .evenOdd : .nonZero
103101
container.view.layer.mask = maskLayer
104-
container.view.backgroundColor = .red // TODO
102+
container.view.backgroundColor = environment.suggestedForegroundColor.uiColor
105103
}
106104
}

0 commit comments

Comments
 (0)