diff --git a/.swift-version b/.swift-version index 3659ea2f..95ee81a4 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -5.8 +5.9 diff --git a/Sources/Lindenmayer/Modules/Module.swift b/Sources/Lindenmayer/Modules/Module.swift index 7f21b700..34795e31 100644 --- a/Sources/Lindenmayer/Modules/Module.swift +++ b/Sources/Lindenmayer/Modules/Module.swift @@ -56,11 +56,10 @@ public extension Module { public extension Module { /// A string representation of the module. var description: String { - let resovledName: String - if name != "" { - resovledName = name + let resovledName: String = if name != "" { + name } else { - resovledName = String(describing: type(of: self)) + String(describing: type(of: self)) } return resovledName } diff --git a/Sources/Lindenmayer/Rendering/SceneKitRenderer.swift b/Sources/Lindenmayer/Rendering/SceneKitRenderer.swift index fbab8859..53b41953 100644 --- a/Sources/Lindenmayer/Rendering/SceneKitRenderer.swift +++ b/Sources/Lindenmayer/Rendering/SceneKitRenderer.swift @@ -11,6 +11,7 @@ import SceneKit import SceneKitDebugTools import simd +@MainActor struct GrowthState { var transform: simd_float4x4 var nodeRef: SCNNode @@ -78,9 +79,10 @@ extension ColorRepresentation { /// - ``rotationAroundZAxisTransform(angle:)`` /// +@MainActor public struct SceneKitRenderer { /// Creates a new SceneKit rendering engine for L-systems. - public init() {} + public nonisolated init() {} func rotateAroundHeadingToVertical(_ full_transform: simd_float4x4) -> Float { // The interpretation of this symbol is a tricky beast. From pg 41 of @@ -250,6 +252,7 @@ public struct SceneKitRenderer { // print("Added cylinder (r=\(cmd.radius)) by \(cmd.length) at \(String(describing: node.simdTransform))") // print("Moving +y by \(cmd.length) -> \(String(describing: currentState.transform))") } + case TurtleCodes.cone.rawValue: if let cmd = cmd as? RenderCommand.Cone { let node = SCNNode(geometry: SCNCone(topRadius: cmd.radiusTop, bottomRadius: cmd.radiusBottom, height: cmd.length)) @@ -272,6 +275,7 @@ public struct SceneKitRenderer { // print("Added cone (tr=\(cmd.radiusTop), br=\(cmd.radiusBottom) by \(cmd.length) at \(String(describing: node.simdTransform))") // print("Moving +y by \(cmd.length) -> \(String(describing: currentState.transform))") } + case TurtleCodes.sphere.rawValue: if let cmd = cmd as? RenderCommand.Sphere { let node = SCNNode(geometry: SCNSphere(radius: cmd.radius)) @@ -288,6 +292,7 @@ public struct SceneKitRenderer { // print("Added sphere (r=\(cmd.radius)) at \(String(describing: node.simdTransform))") // print("Moving +y by \(radius) -> \(String(describing: currentState.transform))") } + default: // ignore break } // switch cmd.name diff --git a/Sources/LindenmayerViews/Debug/RollToVerticalTestView.swift b/Sources/LindenmayerViews/Debug/RollToVerticalTestView.swift index cec0a3dd..3e1d8c5f 100644 --- a/Sources/LindenmayerViews/Debug/RollToVerticalTestView.swift +++ b/Sources/LindenmayerViews/Debug/RollToVerticalTestView.swift @@ -11,6 +11,7 @@ import SceneKitDebugTools import simd import SwiftUI +@MainActor public struct RollToVerticalTestView: View { let scene: SCNScene let pointSphere0: SCNNode diff --git a/Sources/LindenmayerViews/Dynamic2DLSystemViews.swift b/Sources/LindenmayerViews/Dynamic2DLSystemViews.swift index 32503947..09eec172 100644 --- a/Sources/LindenmayerViews/Dynamic2DLSystemViews.swift +++ b/Sources/LindenmayerViews/Dynamic2DLSystemViews.swift @@ -24,17 +24,17 @@ public struct Dynamic2DLSystemViews: View { public var lsystem: LindenmayerSystem { switch self { case .algae: - return Examples2D.algae + Examples2D.algae case .sierpinskiTriangle: - return Examples2D.sierpinskiTriangle + Examples2D.sierpinskiTriangle case .kochCurve: - return Examples2D.kochCurve + Examples2D.kochCurve case .dragonCurve: - return Examples2D.dragonCurve + Examples2D.dragonCurve case .fractalTree: - return Examples2D.fractalTree + Examples2D.fractalTree case .barnsleyFern: - return Examples2D.barnsleyFern + Examples2D.barnsleyFern } } } diff --git a/Sources/LindenmayerViews/ViewComponents/StateSelectorView.swift b/Sources/LindenmayerViews/ViewComponents/StateSelectorView.swift index edf44d47..61b44471 100644 --- a/Sources/LindenmayerViews/ViewComponents/StateSelectorView.swift +++ b/Sources/LindenmayerViews/ViewComponents/StateSelectorView.swift @@ -5,8 +5,9 @@ // Created by Joseph Heck on 1/10/22. // +import Combine import Lindenmayer -@preconcurrency import SwiftUI +import SwiftUI /// A view that provides a visual representation of the states of an L-system and allows the person viewing it to select an index position from that L-system's state. @available(macOS 12.0, iOS 15.0, *) @@ -25,6 +26,8 @@ public struct StateSelectorView: View { @State private var isLongPressingForward: Bool = false @State private var forwardTimer: Timer? + let indexJumpPublisher: PassthroughSubject = PassthroughSubject() + public var body: some View { VStack { ScrollViewReader { proxy in @@ -37,6 +40,7 @@ public struct StateSelectorView: View { if indexPosition < system.state.count - 1 { indexPosition += 1 proxy.scrollTo(indexPosition) + indexJumpPublisher.send(indexPosition) } } label: { Image(systemName: "plus.square") @@ -44,7 +48,7 @@ public struct StateSelectorView: View { Button { if indexPosition > 1 { indexPosition -= 1 - proxy.scrollTo(indexPosition) + indexJumpPublisher.send(indexPosition) } } label: { Image(systemName: "minus.square") @@ -58,7 +62,7 @@ public struct StateSelectorView: View { step: 1, onEditingChanged: { _ in indexPosition = Int(sliderPosition) - proxy.scrollTo(indexPosition) + indexJumpPublisher.send(indexPosition) }) .onChange(of: indexPosition) { newValue in sliderPosition = Double(newValue) @@ -77,7 +81,6 @@ public struct StateSelectorView: View { if sliderPosition >= 1.0 { sliderPosition -= 1 indexPosition -= 1 - proxy.scrollTo(indexPosition) } } } label: { @@ -93,7 +96,7 @@ public struct StateSelectorView: View { if sliderPosition >= 1.0 { sliderPosition -= 1 indexPosition -= 1 - proxy.scrollTo(indexPosition) + indexJumpPublisher.send(indexPosition) } } } label: { @@ -114,7 +117,7 @@ public struct StateSelectorView: View { if sliderPosition >= 1.0 { sliderPosition -= 1 indexPosition -= 1 - proxy.scrollTo(indexPosition) + indexJumpPublisher.send(indexPosition) } } } @@ -135,7 +138,7 @@ public struct StateSelectorView: View { if sliderPosition < Double(system.state.count - 1) { sliderPosition += 1 indexPosition += 1 - proxy.scrollTo(indexPosition) + indexJumpPublisher.send(indexPosition) } } } label: { @@ -151,7 +154,7 @@ public struct StateSelectorView: View { if sliderPosition < Double(system.state.count - 1) { sliderPosition += 1 indexPosition += 1 - proxy.scrollTo(indexPosition) + indexJumpPublisher.send(indexPosition) } } } label: { @@ -172,7 +175,7 @@ public struct StateSelectorView: View { if sliderPosition < Double(system.state.count - 1) { sliderPosition += 1 indexPosition += 1 - proxy.scrollTo(indexPosition) + indexJumpPublisher.send(indexPosition) } } } @@ -181,10 +184,13 @@ public struct StateSelectorView: View { .keyboardShortcut(KeyboardShortcut(.rightArrow)) #endif } + .onReceive(indexJumpPublisher) { indexPosition in + proxy.scrollTo(indexPosition) + } if _withDetailView { ModuleDetailView(module: system.state(at: indexPosition)) } - } + } // proxy } } diff --git a/Tests/LindenmayerTests/RollUpToVerticalTests.swift b/Tests/LindenmayerTests/RollUpToVerticalTests.swift index 9b309d0e..d8c6e53c 100644 --- a/Tests/LindenmayerTests/RollUpToVerticalTests.swift +++ b/Tests/LindenmayerTests/RollUpToVerticalTests.swift @@ -22,8 +22,9 @@ final class RollUpToVerticalTests: XCTestCase { simd_float4(4.4409075, 13.833499, 8.220964, 1.0) ) - static var renderer = SceneKitRenderer() + static let renderer = SceneKitRenderer() + @MainActor func testMatchingEulerAngles() throws { let node = SCNNode() node.simdTransform = RollUpToVerticalTests.transform_119 @@ -32,6 +33,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(node.simdEulerAngles.z, -3.031, accuracy: 0.001) // roll } + @MainActor func testApplyingAffineTransformsToA3DPoint() throws { let pt = simd_float4(0, 0, 1, 1) // x=0, y=0, z=1 let result = matrix_multiply(RollUpToVerticalTests.transform_119, pt) @@ -55,6 +57,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(result3.z, result.z - RollUpToVerticalTests.transform_119.columns.3.z, accuracy: 0.001) } + @MainActor func testApplyingKnownTransformsToA3DPoint() throws { let pt = simd_float3(0, 0, 1) // x=0, y=0, z=1 let rotate_90_around_Y = SceneKitRenderer.rotationAroundYAxisTransform(angle: Angle(degrees: 90)).rotationTransform @@ -86,6 +89,7 @@ final class RollUpToVerticalTests: XCTestCase { // print(matrix_identity_float4x4.prettyPrintString()) } + @MainActor func testHeadingVectorRotating45degToRight() throws { // This rotation is a "right turn" from the starting position, straight up. We rotation 45° around // the Z axis - negative rotation to make it to the right. @@ -102,6 +106,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(heading_vector.z, 0, accuracy: 0.0001) } + @MainActor func testHeadingVectorRotating45degToLeft() throws { // This rotation is a "right turn" from the starting position, straight up. // We rotate 45° around the Z axis - negative rotation to make it to the right. @@ -117,6 +122,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(heading_vector.z, 0, accuracy: 0.0001) } + @MainActor func testHeadingVectorRotating45degPitchDown() throws { // This rotation is a "pitch up" from the starting position, straight up. // We rotate 45° around the X axis - negative rotation to make it pitch 'down'. @@ -128,6 +134,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(heading_vector.z, -0.7071067, accuracy: 0.0001) } + @MainActor func testHeadingVectorRotating45degPitchUp() throws { // This rotation is a "pitch down" from the starting position, straight up. // We rotate 45° around the X axis - positive rotation to make it to pitch 'up'. @@ -138,6 +145,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(heading_vector.z, 0.7071067, accuracy: 0.0001) } + @MainActor func testHeadingVectorRotating45degYaw() throws { // This rotation is a "pitch down" from the starting position, straight up. // We rotate 45° around the X axis - positive rotation to make it to pitch 'up'. @@ -148,6 +156,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(heading_vector.z, 0, accuracy: 0.0001) } + @MainActor func testUpVectorAfterRotation() throws { let original_up_vector = simd_float3(x: 0, y: 0, z: 1) // roll to the right @@ -160,6 +169,7 @@ final class RollUpToVerticalTests: XCTestCase { // MARK: - testing rotation angle calculations + @MainActor func testRotatingIdentityMatrix() throws { // Identity state vector indicates we've not moved from start, so the forward vector is +Y and the // up vector is +Z. No amount of rotation will do us any good, since the plane we're rotating on (the X-Z plane) @@ -169,6 +179,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(0, rotation_on_XZ_plane) } + @MainActor func testRotating45degToRightMatrix() throws { // This rotation is a "right turn" from the starting position, straight up. We rotation 45° around // the Z axis - negative rotation to make it to the right. @@ -180,6 +191,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(Float.pi / 2, rotation_on_plane, accuracy: 0.0001) } + @MainActor func testRotating45degToLeftMatrix() throws { // This rotation is a "right turn" from the starting position, straight up. We rotation 45° around // the Z axis - negative rotation to make it to the right. @@ -191,6 +203,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(-Float.pi / 2, rotation_on_plane, accuracy: 0.0001) } + @MainActor func testRotating45degPitchUpMatrix() throws { // This rotation is a "pitch up" from the starting position, straight up. We rotate 45° around // the X axis - negative rotation to make it pitch 'up'. @@ -202,6 +215,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(Float.pi, rotation_on_plane, accuracy: 0.0001) } + @MainActor func testRotating45degPitchDownMatrix() throws { // This rotation is a "pitch down" from the starting position, straight up. We rotate 45° around // the X axis - positive rotation to make it to pitch 'down'. @@ -213,6 +227,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(0, rotation_on_plane, accuracy: 0.0001) } + @MainActor func testRotating90degPitchUpMatrix() throws { // Since we start facing straight up, pitching up another 90° results in us facing the +z direction // with the "up" vector now rotated to point pretty much straight in the -Y direction. @@ -223,6 +238,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(Float.pi, abs(rotation_on_plane), accuracy: 0.0001) } + @MainActor func testRotating90degPitchDownMatrix() throws { // Since we start facing straight up, pitching up another 90° results in us facing the +z direction // with the "up" vector now rotated to point pretty much straight in the -Y direction. @@ -232,6 +248,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(0, rotation_on_plane, accuracy: 0.0001) } + @MainActor func testRotating90degToRight() throws { // Since we start facing straight up, pitching up another 90° results in us facing the +z direction // with the "up" vector now rotated to point pretty much straight in the -Y direction. @@ -241,6 +258,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(Float.pi / 2, rotation_on_plane, accuracy: 0.0001) } + @MainActor func testRotating90degToLeft() throws { // Since we start facing straight up, pitching up another 90° results in us facing the +z direction // with the "up" vector now rotated to point pretty much straight in the -Y direction. @@ -250,6 +268,7 @@ final class RollUpToVerticalTests: XCTestCase { XCTAssertEqual(-Float.pi / 2, rotation_on_plane, accuracy: 0.0001) } + @MainActor func rotationToVerticalTestCodeOriginal(_ full_transform: simd_float4x4) throws -> Float { // NOTE(heckj): Leaving this here - but the implementation doesn't pass the tests // because it's not returning the correct "rotation angle" (positive/negative). diff --git a/Tests/LindenmayerTests/TransformTests.swift b/Tests/LindenmayerTests/TransformTests.swift index bbd923c3..efcb6dbf 100644 --- a/Tests/LindenmayerTests/TransformTests.swift +++ b/Tests/LindenmayerTests/TransformTests.swift @@ -10,6 +10,7 @@ import simd import XCTest final class TransformTests: XCTestCase { + @MainActor func testTranslationTransformMatchesSceneKit() throws { let node = SCNNode() let x = 1.5 @@ -23,6 +24,7 @@ final class TransformTests: XCTestCase { XCTAssertEqual(transform, node.simdTransform) } + @MainActor func testScalingTransformMatchesSceneKit() throws { let node = SCNNode() let x = 1.5 @@ -36,6 +38,7 @@ final class TransformTests: XCTestCase { XCTAssertEqual(transform, node.simdTransform) } + @MainActor func testRollTransformMatchesSceneKit() throws { let node = SCNNode() let angle = Angle(degrees: 30) @@ -47,6 +50,7 @@ final class TransformTests: XCTestCase { XCTAssertEqual(transform, node.simdTransform) } + @MainActor func testYawTransformMatchesSceneKit() throws { let node = SCNNode() let angle = Angle(degrees: 30) @@ -58,6 +62,7 @@ final class TransformTests: XCTestCase { XCTAssertEqual(transform, node.simdTransform) } + @MainActor func testPitchTransformMatchesSceneKit() throws { let node = SCNNode() let angle = Angle(degrees: 30)