Last active
October 14, 2023 06:15
-
-
Save JadenGeller/249fa8e50514a63442593e8212ca9ea1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import simd | |
struct Rotation3DEffect: GeometryEffect { | |
var angle: Angle | |
var axis: (x: CGFloat, y: CGFloat, z: CGFloat) | |
var anchor: UnitPoint = .center | |
var anchorZ: CGFloat = 0 | |
var focalLength: CGFloat | |
var unitTransform3D: CATransform3D { | |
var rotate = CATransform3DIdentity | |
rotate.m34 = -1 / focalLength | |
rotate = CATransform3DRotate(rotate, CGFloat(angle.radians), axis.x, axis.y, axis.z) | |
let translateToAnchor = CATransform3DMakeTranslation(-anchor.x, -anchor.y, -anchorZ) | |
let translateFromAnchor = CATransform3DMakeTranslation(anchor.x, anchor.y, anchorZ) | |
return CATransform3DConcat(translateToAnchor, CATransform3DConcat(rotate, translateFromAnchor)) | |
} | |
func effectValue(size: CGSize) -> ProjectionTransform { | |
.init(CATransform3DConcat( | |
CATransform3DMakeScale(1 / size.width, 1 / size.height, 1), | |
CATransform3DConcat(unitTransform3D, CATransform3DMakeScale(size.width, size.height, 1)) | |
)) | |
} | |
struct MeasurementProxy { | |
var transform: simd_double4x4 | |
func transform(_ anchor: UnitPoint) -> UnitPoint { | |
let result = transform * SIMD4(anchor.x, anchor.y, 0, 1) | |
return .init(x: result.x / result.w, y: result.y / result.w) | |
} | |
func length(of segment: (UnitPoint, UnitPoint)) -> CGFloat { | |
let segment = (transform(segment.0), transform(segment.1)) | |
return sqrt(pow(segment.1.x - segment.0.x, 2) + pow(segment.1.y - segment.0.y, 2)) | |
} | |
func bounds(for size: CGSize) -> CGRect { | |
var minPoint = CGPoint(x: Double.infinity, y: Double.infinity) | |
var maxPoint = CGPoint(x: -Double.infinity, y: -Double.infinity) | |
for anchor in [.topLeading, .topTrailing, .bottomLeading, .bottomTrailing] as [UnitPoint] { | |
let point = transform(anchor) | |
minPoint.x = min(minPoint.x, point.x) | |
minPoint.y = min(minPoint.y, point.y) | |
maxPoint.x = max(maxPoint.x, point.x) | |
maxPoint.y = max(maxPoint.y, point.y) | |
} | |
return .init( | |
x: minPoint.x * size.width, | |
y: minPoint.y * size.height, | |
width: (maxPoint.x - minPoint.x) * size.width, | |
height: (maxPoint.y - minPoint.y) * size.height | |
) | |
} | |
} | |
var measurements: MeasurementProxy { | |
return .init(transform: unitTransform3D.matrix) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct BoundsLayout: Layout { | |
var bounds: (ProposedViewSize, LayoutSubview) -> CGRect | |
func makeCache(subviews: Subviews) -> CGRect { | |
.zero | |
} | |
func sizeThatFits( | |
proposal: ProposedViewSize, | |
subviews: Subviews, | |
cache: inout CGRect | |
) -> CGSize { | |
precondition(subviews.count == 1) | |
cache = bounds(proposal, subviews[0]) | |
return cache.size | |
} | |
func placeSubviews( | |
in bounds: CGRect, | |
proposal: ProposedViewSize, | |
subviews: Subviews, | |
cache: inout CGRect | |
) { | |
precondition(subviews.count == 1) | |
subviews[0].place(at: .init(x: bounds.origin.x - cache.origin.x, y: bounds.origin.y - cache.origin.y), anchor: .topLeading, proposal: proposal) | |
} | |
func spacing( | |
subviews: Self.Subviews, | |
cache: inout Self.Cache | |
) -> ViewSpacing { | |
.zero | |
} | |
} | |
extension View { | |
func layoutBounds(_ bounds: @escaping (ProposedViewSize, LayoutSubview) -> CGRect) -> some View { | |
BoundsLayout(bounds: bounds) { | |
self | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct ContentView: View { | |
@State var size: CGSize = .init(width: 300, height: 300) | |
@State var alpha: CGFloat = 0.5 | |
@State var axis: (x: CGFloat, y: CGFloat, z: CGFloat) = (0, 1, 0) | |
@State var anchor: UnitPoint = .leading | |
@State var anchorZ: CGFloat = 0 | |
@State var focalLength: CGFloat = 1 | |
@State var angle: Angle = .degrees(30) | |
var rotation: Rotation3DEffect { | |
.init(angle: angle, axis: axis, anchor: anchor, anchorZ: anchorZ, focalLength: focalLength) | |
} | |
var body: some View { | |
Slider(value: $size.width, in: 50...300) { | |
Text("Size (width): \(size.width)") | |
} | |
Slider(value: $size.height, in: 50...300) { | |
Text("Size (height): \(size.height)") | |
} | |
Slider(value: $alpha, in: 0...1) { | |
Text("Alpha: \(alpha)") | |
} | |
Slider(value: $angle.degrees, in: -90...90) { | |
Text("Angle (degrees): \(angle.degrees)") | |
} | |
Slider(value: $anchor.x, in: 0...1) { | |
Text("Anchor (x): \(anchor.x)") | |
} | |
Slider(value: $anchor.y, in: 0...1) { | |
Text("Anchor (y): \(anchor.y)") | |
} | |
Slider(value: $anchorZ, in: -0...1) { | |
Text("Anchor (z): \(anchorZ)") | |
} | |
Slider(value: $axis.x, in: 0...1) { | |
Text("Axis (x): \(axis.x)") | |
} | |
Slider(value: $axis.y, in: 0...1) { | |
Text("Axis (y): \(axis.y)") | |
} | |
Slider(value: $axis.z, in: 0...1) { | |
Text("Axis (z): \(axis.z)") | |
} | |
Slider(value: $focalLength, in: 0...2) { | |
Text("Focal Length: \(focalLength)") | |
} | |
Color.yellow.opacity(alpha) | |
.frame(width: size.width, height: size.height) | |
.modifier(rotation) | |
.layoutBounds { proposal, view in | |
rotation.measurements.bounds(for: view.sizeThatFits(proposal)) | |
} | |
.background(.black) | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment