From cc9c8698c5482830f7f6119e3372d14ef471f650 Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Sat, 7 May 2022 12:50:47 +0100 Subject: [PATCH 1/7] =?UTF-8?q?fixing=20issues=20with=20simulator=20proper?= =?UTF-8?q?ties.=20Simulator=20now=20can=20import=20arkit=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Package.swift | 20 ++++---- .../FocusEntity/FocusEntity+Alignment.swift | 10 +++- Sources/FocusEntity/FocusEntity+Classic.swift | 6 +-- Sources/FocusEntity/FocusEntity+Colored.swift | 4 +- Sources/FocusEntity/FocusEntity+Segment.swift | 8 +-- Sources/FocusEntity/FocusEntity.swift | 51 ++++++++++--------- 6 files changed, 52 insertions(+), 47 deletions(-) diff --git a/Package.swift b/Package.swift index 20419a7..74b78ed 100644 --- a/Package.swift +++ b/Package.swift @@ -4,14 +4,14 @@ import PackageDescription let package = Package( - name: "FocusEntity", - platforms: [.iOS(.v13), .macOS(.v10_15)], - products: [ - .library(name: "FocusEntity", targets: ["FocusEntity"]) - ], - dependencies: [], - targets: [ - .target(name: "FocusEntity", dependencies: []) - ], - swiftLanguageVersions: [.v5] + name: "FocusEntity", + platforms: [.iOS(.v13), .macOS(.v10_15)], + products: [ + .library(name: "FocusEntity", targets: ["FocusEntity"]) + ], + dependencies: [], + targets: [ + .target(name: "FocusEntity", dependencies: []) + ], + swiftLanguageVersions: [.v5] ) diff --git a/Sources/FocusEntity/FocusEntity+Alignment.swift b/Sources/FocusEntity/FocusEntity+Alignment.swift index 5eda5f5..6b0ea55 100644 --- a/Sources/FocusEntity/FocusEntity+Alignment.swift +++ b/Sources/FocusEntity/FocusEntity+Alignment.swift @@ -6,9 +6,10 @@ // Copyright ยฉ 2019 Max Cobb. All rights reserved. // -#if canImport(ARKit) && !targetEnvironment(simulator) import RealityKit +#if canImport(ARKit) import ARKit +#endif import Combine extension FocusEntity { @@ -27,6 +28,7 @@ extension FocusEntity { self.position = average } + #if canImport(ARKit) /// Update the transform of the focus square to be aligned with the camera. internal func updateTransform(raycastResult: ARRaycastResult) { self.updatePosition() @@ -90,6 +92,7 @@ extension FocusEntity { orientation = targetAlignment } } + #endif internal func normalize(_ angle: Float, forMinimalRotationTo ref: Float) -> Float { // Normalize angle in steps of 90 degrees such that the rotation to the other angle is minimal @@ -112,6 +115,7 @@ extension FocusEntity { return (camTransform.translation, -[camDirection.x, camDirection.y, camDirection.z]) } + #if canImport(ARKit) /// - Parameters: /// - Returns: ARRaycastResult if an existing plane geometry or an estimated plane are found, otherwise nil. internal func smartRaycast() -> ARRaycastResult? { @@ -135,6 +139,7 @@ extension FocusEntity { // 2. As a fallback, check for a result on estimated planes. return results.first(where: { $0.target == .estimatedPlane }) } + #endif /// Uses interpolation between orientations to create a smooth `easeOut` orientation adjustment animation. internal func performAlignmentAnimation(to newOrientation: simd_quatf) { @@ -153,6 +158,7 @@ extension FocusEntity { return vectorsDot < 0.999 } + #if canImport(ARKit) /** Reduce visual size change with distance by scaling up when close and down when far away. @@ -170,5 +176,5 @@ extension FocusEntity { return 0.25 * distanceFromCamera + 0.825 } } + #endif } -#endif diff --git a/Sources/FocusEntity/FocusEntity+Classic.swift b/Sources/FocusEntity/FocusEntity+Classic.swift index cb64ce3..738bfcb 100644 --- a/Sources/FocusEntity/FocusEntity+Classic.swift +++ b/Sources/FocusEntity/FocusEntity+Classic.swift @@ -6,7 +6,6 @@ // Copyright ยฉ 2019 Max Cobb. All rights reserved. // -#if canImport(ARKit) && !targetEnvironment(simulator) import RealityKit /// An extension of FocusEntity holding the methods for the "classic" style. @@ -76,9 +75,6 @@ internal extension FocusEntity { } self.positioningEntity.scale = SIMD3(repeating: FocusEntity.size * FocusEntity.scaleForClosedSquare) - - // Always render focus square on top of other content. -// self.displayNodeHierarchyOnTop(true) } // MARK: Animations @@ -113,4 +109,4 @@ internal extension FocusEntity { } } -#endif + diff --git a/Sources/FocusEntity/FocusEntity+Colored.swift b/Sources/FocusEntity/FocusEntity+Colored.swift index 595b198..9e86901 100644 --- a/Sources/FocusEntity/FocusEntity+Colored.swift +++ b/Sources/FocusEntity/FocusEntity+Colored.swift @@ -6,7 +6,6 @@ // Copyright ยฉ 2019 Max Cobb. All rights reserved. // -#if canImport(ARKit) && !targetEnvironment(simulator) import RealityKit /// An extension of FocusEntity holding the methods for the "colored" style. @@ -26,7 +25,7 @@ public extension FocusEntity { self.fillPlane?.model?.materials = [SimpleMaterial()] } var modelMaterial = UnlitMaterial(color: .clear) - if #available(iOS 15, *) { + if #available(iOS 15, macOS 12, *) { switch endColor { case .color(let uikitColour): modelMaterial.color = .init(tint: uikitColour, texture: nil) @@ -42,4 +41,3 @@ public extension FocusEntity { self.fillPlane?.model?.materials[0] = modelMaterial } } -#endif diff --git a/Sources/FocusEntity/FocusEntity+Segment.swift b/Sources/FocusEntity/FocusEntity+Segment.swift index f4227ef..57b39e3 100644 --- a/Sources/FocusEntity/FocusEntity+Segment.swift +++ b/Sources/FocusEntity/FocusEntity+Segment.swift @@ -12,13 +12,13 @@ internal extension FocusEntity { /* The focus square consists of eight segments as follows, which can be individually animated. - s0 s1 - _ _ + s0 s1 + _ _ s2 | | s3 s4 | | s5 - - - - s6 s7 + - - + s6 s7 */ enum Corner { case topLeft // s0, s2 diff --git a/Sources/FocusEntity/FocusEntity.swift b/Sources/FocusEntity/FocusEntity.swift index 102095a..70714d9 100644 --- a/Sources/FocusEntity/FocusEntity.swift +++ b/Sources/FocusEntity/FocusEntity.swift @@ -6,6 +6,7 @@ // Copyright ยฉ 2019 Max Cobb. All rights reserved. // +import Foundation import RealityKit #if canImport(RealityFoundation) import RealityFoundation @@ -15,8 +16,9 @@ import RealityFoundation #warning("FocusEntity: This package is only fully available with physical iOS devices") #endif -#if canImport(ARKit) && !targetEnvironment(simulator) +#if canImport(ARKit) import ARKit +#endif import Combine public protocol HasFocusEntity: Entity {} @@ -34,10 +36,12 @@ public extension HasFocusEntity { get { self.focus.segments } set { self.focus.segments = newValue } } + #if canImport(ARKit) var allowedRaycast: ARRaycastQuery.Target { get { self.focus.allowedRaycast } set { self.focus.allowedRaycast = newValue } } + #endif } @objc public protocol FocusEntityDelegate { @@ -92,14 +96,15 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { public func setAutoUpdate(to autoUpdate: Bool) { guard autoUpdate != self.isAutoUpdating, - !(autoUpdate && self.arView == nil) else { - return - } + !(autoUpdate && self.arView == nil) + else { return } self.updateCancellable?.cancel() if autoUpdate { + #if canImport(ARKit) self.updateCancellable = self.myScene?.subscribe( to: SceneEvents.Update.self, self.updateFocusEntity ) + #endif } self.isAutoUpdating = autoUpdate } @@ -108,7 +113,9 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { // MARK: - Types public enum State: Equatable { case initializing + #if canImport(ARKit) case tracking(raycastResult: ARRaycastResult, camera: ARCamera?) + #endif } // MARK: - Properties @@ -117,14 +124,18 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { var lastPosition: SIMD3? { switch state { case .initializing: return nil + #if canImport(ARKit) case .tracking(let raycastResult, _): return raycastResult.worldTransform.translation + #endif } } + #if canImport(ARKit) fileprivate func entityOffPlane(_ raycastResult: ARRaycastResult, _ camera: ARCamera?) { self.onPlane = false displayOffPlane(for: raycastResult) } + #endif public var state: State = .initializing { didSet { @@ -136,6 +147,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { displayAsBillboard() self.delegate?.toInitializingState?() } + #if canImport(ARKit) case let .tracking(raycastResult, camera): let stateChanged = oldValue == .initializing if stateChanged && self.anchor != nil { @@ -151,6 +163,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { if stateChanged { self.delegate?.toTrackingState?() } + #endif } } } @@ -166,20 +179,22 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { /// A camera anchor used for placing the focus entity in front of the camera. internal var cameraAnchor: AnchorEntity! + #if canImport(ARKit) /// The focus square's current alignment. internal var currentAlignment: ARPlaneAnchor.Alignment? /// The current plane anchor if the focus square is on a plane. public internal(set) var currentPlaneAnchor: ARPlaneAnchor? - /// The focus square's most recent positions. - internal var recentFocusEntityPositions: [SIMD3] = [] - /// The focus square's most recent alignments. internal var recentFocusEntityAlignments: [ARPlaneAnchor.Alignment] = [] /// Previously visited plane anchors. internal var anchorsOfVisitedPlanes: Set = [] + #endif + + /// The focus square's most recent positions. + internal var recentFocusEntityPositions: [SIMD3] = [] /// The primary node that controls the position of other `FocusEntity` nodes. internal let positioningEntity = Entity() @@ -245,7 +260,9 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { /// Displays the focus square parallel to the camera plane. private func displayAsBillboard() { self.onPlane = false + #if canImport(ARKit) self.currentAlignment = .none + #endif stateChangedSetup() } @@ -263,6 +280,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { performAlignmentAnimation(to: newRotation) } + #if canImport(ARKit) /// Called when a surface has been detected. private func displayOffPlane(for raycastResult: ARRaycastResult) { self.stateChangedSetup() @@ -293,6 +311,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { } updateTransform(raycastResult: raycastResult) } + #endif /// Called whenever the state of the focus entity changes /// @@ -315,6 +334,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { self.stateChanged(newPlane: newPlane) } + #if canImport(ARKit) public func updateFocusEntity(event: SceneEvents.Update? = nil) { // Perform hit testing only when ARKit tracking is in a good state. guard let camera = self.arView?.session.currentFrame?.camera, @@ -329,20 +349,5 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { self.state = .tracking(raycastResult: result, camera: camera) } + #endif } -#else -/** - FocusEntity is only enabled for environments which can import ARKit. - */ -open class FocusEntity { - public convenience init(on arView: ARView, style: FocusEntityComponent.Style) { - self.init(on: arView, focus: FocusEntityComponent(style: style)) - } - public convenience init(on arView: ARView, focus: FocusEntityComponent) { - self.init() - } - internal init() { - print("This is only supported on a physical iOS device.") - } -} -#endif From 4c61ada73501624c03f88cad1fc0df684a77ee27 Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Sun, 8 May 2022 16:52:08 +0100 Subject: [PATCH 2/7] Added documentation, fix swiftlints, added new delegate method. --- .../FocusEntity-Example/AppDelegate.swift | 28 +++--- .../FocusEntity-Example/ContentView.swift | 23 ++--- .../FocusEntity-Example/FocusARView.swift | 91 +++++++++---------- Sources/FocusEntity/FocusEntity+Classic.swift | 1 - Sources/FocusEntity/FocusEntity.swift | 27 ++++-- .../FocusEntity/FocusEntityComponent.swift | 22 ++++- 6 files changed, 104 insertions(+), 88 deletions(-) diff --git a/FocusEntity-Example/FocusEntity-Example/AppDelegate.swift b/FocusEntity-Example/FocusEntity-Example/AppDelegate.swift index 636a9d4..4e0ce8f 100644 --- a/FocusEntity-Example/FocusEntity-Example/AppDelegate.swift +++ b/FocusEntity-Example/FocusEntity-Example/AppDelegate.swift @@ -12,21 +12,21 @@ import SwiftUI @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? + var window: UIWindow? - func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { - // Create the SwiftUI view that provides the window contents. - let contentView = ContentView() + // Create the SwiftUI view that provides the window contents. + let contentView = ContentView() - // Use a UIHostingController as window root view controller. - let window = UIWindow(frame: UIScreen.main.bounds) - window.rootViewController = UIHostingController(rootView: contentView) - self.window = window - window.makeKeyAndVisible() - return true - } + // Use a UIHostingController as window root view controller. + let window = UIWindow(frame: UIScreen.main.bounds) + window.rootViewController = UIHostingController(rootView: contentView) + self.window = window + window.makeKeyAndVisible() + return true + } } diff --git a/FocusEntity-Example/FocusEntity-Example/ContentView.swift b/FocusEntity-Example/FocusEntity-Example/ContentView.swift index d75ffb8..0b3a196 100644 --- a/FocusEntity-Example/FocusEntity-Example/ContentView.swift +++ b/FocusEntity-Example/FocusEntity-Example/ContentView.swift @@ -10,25 +10,22 @@ import SwiftUI import RealityKit struct ContentView: View { - var body: some View { - ARViewContainer().edgesIgnoringSafeArea(.all) - } + var body: some View { + ARViewContainer().edgesIgnoringSafeArea(.all) + } } struct ARViewContainer: UIViewRepresentable { - - func makeUIView(context: Context) -> FocusARView { - FocusARView(frame: .zero) - } - - func updateUIView(_ uiView: FocusARView, context: Context) {} - + func makeUIView(context: Context) -> FocusARView { + FocusARView(frame: .zero) + } + func updateUIView(_ uiView: FocusARView, context: Context) {} } #if DEBUG struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } + static var previews: some View { + ContentView() + } } #endif diff --git a/FocusEntity-Example/FocusEntity-Example/FocusARView.swift b/FocusEntity-Example/FocusEntity-Example/FocusARView.swift index 4c0cd5e..4ad51ef 100644 --- a/FocusEntity-Example/FocusEntity-Example/FocusARView.swift +++ b/FocusEntity-Example/FocusEntity-Example/FocusARView.swift @@ -12,59 +12,50 @@ import Combine import ARKit class FocusARView: ARView { - enum FocusStyleChoices { - case classic - case material - case color - } + enum FocusStyleChoices { + case classic + case material + case color + } - /// Style to be displayed in the example - let focusStyle: FocusStyleChoices = .classic - var focusEntity: FocusEntity? - required init(frame frameRect: CGRect) { - super.init(frame: frameRect) - self.setupConfig() + /// Style to be displayed in the example + let focusStyle: FocusStyleChoices = .classic + var focusEntity: FocusEntity? + required init(frame frameRect: CGRect) { + super.init(frame: frameRect) + self.setupConfig() - switch self.focusStyle { - case .color: - self.focusEntity = FocusEntity(on: self, focus: .plane) - case .material: - do { - let onColor: MaterialColorParameter = try .texture(.load(named: "Add")) - let offColor: MaterialColorParameter = try .texture(.load(named: "Open")) - self.focusEntity = FocusEntity( - on: self, - style: .colored( - onColor: onColor, offColor: offColor, - nonTrackingColor: offColor - ) - ) - } catch { - self.focusEntity = FocusEntity(on: self, focus: .classic) - print("Unable to load plane textures") - print(error.localizedDescription) - } - default: - self.focusEntity = FocusEntity(on: self, focus: .classic) + switch self.focusStyle { + case .color: + self.focusEntity = FocusEntity(on: self, focus: .plane) + case .material: + do { + let onColor: MaterialColorParameter = try .texture(.load(named: "Add")) + let offColor: MaterialColorParameter = try .texture(.load(named: "Open")) + self.focusEntity = FocusEntity( + on: self, + style: .colored( + onColor: onColor, offColor: offColor, + nonTrackingColor: offColor + ) + ) + } catch { + self.focusEntity = FocusEntity(on: self, focus: .classic) + print("Unable to load plane textures") + print(error.localizedDescription) + } + default: + self.focusEntity = FocusEntity(on: self, focus: .classic) + } } - } - - func setupConfig() { - let config = ARWorldTrackingConfiguration() - config.planeDetection = [.horizontal, .vertical] - session.run(config, options: []) - } - @objc required dynamic init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} + func setupConfig() { + let config = ARWorldTrackingConfiguration() + config.planeDetection = [.horizontal, .vertical] + session.run(config) + } -extension FocusARView: FocusEntityDelegate { - func toTrackingState() { - print("tracking") - } - func toInitializingState() { - print("initializing") - } + @objc required dynamic init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } } diff --git a/Sources/FocusEntity/FocusEntity+Classic.swift b/Sources/FocusEntity/FocusEntity+Classic.swift index 738bfcb..ab4a274 100644 --- a/Sources/FocusEntity/FocusEntity+Classic.swift +++ b/Sources/FocusEntity/FocusEntity+Classic.swift @@ -109,4 +109,3 @@ internal extension FocusEntity { } } - diff --git a/Sources/FocusEntity/FocusEntity.swift b/Sources/FocusEntity/FocusEntity.swift index 70714d9..0c16708 100644 --- a/Sources/FocusEntity/FocusEntity.swift +++ b/Sources/FocusEntity/FocusEntity.swift @@ -44,12 +44,26 @@ public extension HasFocusEntity { #endif } -@objc public protocol FocusEntityDelegate { +public protocol FocusEntityDelegate: AnyObject { /// Called when the FocusEntity is now in world space - @objc optional func toTrackingState() + func toTrackingState() /// Called when the FocusEntity is tracking the camera - @objc optional func toInitializingState() + func toInitializingState() + + func focusEntity( + _ focusEntity: FocusEntity, + trackingUpdated trackingState: FocusEntity.State, + oldState: FocusEntity.State + ) +} + +public extension FocusEntityDelegate { + func toTrackingState() {} + func toInitializingState() {} + func focusEntity( + _ focusEntity: FocusEntity, trackingUpdated trackingState: FocusEntity.State, oldState: FocusEntity.State + ) {} } /** @@ -145,7 +159,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { case .initializing: if oldValue != .initializing { displayAsBillboard() - self.delegate?.toInitializingState?() + self.delegate?.toInitializingState() } #if canImport(ARKit) case let .tracking(raycastResult, camera): @@ -160,8 +174,9 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { entityOffPlane(raycastResult, camera) currentPlaneAnchor = nil } + self.delegate?.focusEntity(self, trackingUpdated: state, oldState: oldValue) if stateChanged { - self.delegate?.toTrackingState?() + self.delegate?.toTrackingState() } #endif } @@ -228,7 +243,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { // Start the focus square as a billboard. displayAsBillboard() - self.delegate?.toInitializingState?() + self.delegate?.toInitializingState() arView.scene.addAnchor(self) self.setAutoUpdate(to: true) switch self.focus.style { diff --git a/Sources/FocusEntity/FocusEntityComponent.swift b/Sources/FocusEntity/FocusEntityComponent.swift index dae8530..3b29ce1 100644 --- a/Sources/FocusEntity/FocusEntityComponent.swift +++ b/Sources/FocusEntity/FocusEntityComponent.swift @@ -27,8 +27,16 @@ internal struct ColoredStyle { } public struct FocusEntityComponent: Component { + /// FocusEntityComponent Style, dictating how the FocusEntity will appear in different states public enum Style { - case classic(color: Material.Color) + /// Default style of FocusEntity. Box that's open when not on a plane, closed when on one. + /// - color: Color of the FocusEntity lines, default: `FocusEntityComponent.defaultColor` + case classic(color: Material.Color = FocusEntityComponent.defaultColor) + /// Style that changes based on state of the FocusEntity + /// - onColor: Color when FocusEntity is tracking on a known surface. + /// - offColor: Color when FocusEntity is tracking, but the exact surface isn't known. + /// - nonTrackingColor: Color when FocusEntity is unable to find a plane or estimate a plane. + /// - mesh: Optional mesh for FocusEntity, default is a 0.1m square plane. case colored( onColor: MaterialColorParameter, offColor: MaterialColorParameter, @@ -59,8 +67,12 @@ public struct FocusEntityComponent: Component { } } - /// Convenient presets - public static let classic = FocusEntityComponent(style: .classic(color: #colorLiteral(red: 1, green: 0.8, blue: 0, alpha: 1))) + /// Default color of FocusEntity + public static let defaultColor = #colorLiteral(red: 1, green: 0.8, blue: 0, alpha: 1) + /// Default style of FocusEntity, using the FocusEntityComponent.Style.classic with the color FocusEntityComponent.defaultColor. + public static let classic = FocusEntityComponent(style: .classic(color: FocusEntityComponent.defaultColor)) + /// Alternative preset for FocusEntity, using FocusEntityComponent.Style.classic.colored, + /// with green, orange and red for the onColor, offColor and nonTrackingColor respectively public static let plane = FocusEntityComponent( style: .colored( onColor: .color(.green), @@ -69,7 +81,7 @@ public struct FocusEntityComponent: Component { mesh: FocusEntityComponent.defaultPlane ) ) - internal var isOpen = true + public internal(set) var isOpen = true internal var segments: [FocusEntity.Segment] = [] #if !os(macOS) public var allowedRaycast: ARRaycastQuery.Target = .estimatedPlane @@ -79,6 +91,8 @@ public struct FocusEntityComponent: Component { width: 0.1, depth: 0.1 ) + /// Create FocusEntityComponent with a given FocusEntityComponent.Style. + /// - Parameter style: FocusEntityComponent Style, dictating how the FocusEntity will appear in different states. public init(style: Style) { self.style = style // If the device has LiDAR, then default behaviour is to only allow From b56844de21e6056174435c10e04e55bf7242bdb8 Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Mon, 9 May 2022 10:18:19 +0100 Subject: [PATCH 3/7] added delegate for plane changed, and documentation --- Sources/FocusEntity/FocusEntity.swift | 34 +++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/Sources/FocusEntity/FocusEntity.swift b/Sources/FocusEntity/FocusEntity.swift index 0c16708..cc57ec8 100644 --- a/Sources/FocusEntity/FocusEntity.swift +++ b/Sources/FocusEntity/FocusEntity.swift @@ -51,11 +51,27 @@ public protocol FocusEntityDelegate: AnyObject { /// Called when the FocusEntity is tracking the camera func toInitializingState() + /// When the tracking state of the FocusEntity updates. This will be called every update frame. + /// - Parameters: + /// - focusEntity: FocusEntity object whose tracking state has changed. + /// - trackingState: New tracking state of the focus entity. + /// - oldState: Old tracking state of the focus entity. func focusEntity( _ focusEntity: FocusEntity, trackingUpdated trackingState: FocusEntity.State, oldState: FocusEntity.State ) + + /// When the plane this focus entity is tracking changes. If the focus entity moves around within one plane anchor there will be no calls. + /// - Parameters: + /// - focusEntity: FocusEntity object whose anchor has changed. + /// - planeChanged: New anchor the focus entity is tracked to. + /// - oldPlane: Previous anchor the focus entity is tracked to. + func focusEntity( + _ focusEntity: FocusEntity, + planeChanged: ARPlaneAnchor?, + oldPlane: ARPlaneAnchor? + ) } public extension FocusEntityDelegate { @@ -64,6 +80,7 @@ public extension FocusEntityDelegate { func focusEntity( _ focusEntity: FocusEntity, trackingUpdated trackingState: FocusEntity.State, oldState: FocusEntity.State ) {} + func focusEntity(_ focusEntity: FocusEntity, planeChanged: ARPlaneAnchor?, oldPlane: ARPlaneAnchor?) {} } /** @@ -167,19 +184,19 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { if stateChanged && self.anchor != nil { self.anchoring = AnchoringComponent(.world(transform: Transform.identity.matrix)) } - if let planeAnchor = raycastResult.anchor as? ARPlaneAnchor { + let planeAnchor = raycastResult.anchor as? ARPlaneAnchor + if let planeAnchor = planeAnchor { entityOnPlane(for: raycastResult, planeAnchor: planeAnchor) - currentPlaneAnchor = planeAnchor } else { entityOffPlane(raycastResult, camera) - currentPlaneAnchor = nil } - self.delegate?.focusEntity(self, trackingUpdated: state, oldState: oldValue) + defer { currentPlaneAnchor = planeAnchor } if stateChanged { self.delegate?.toTrackingState() } #endif } + self.delegate?.focusEntity(self, trackingUpdated: state, oldState: oldValue) } } @@ -199,7 +216,14 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { internal var currentAlignment: ARPlaneAnchor.Alignment? /// The current plane anchor if the focus square is on a plane. - public internal(set) var currentPlaneAnchor: ARPlaneAnchor? + public internal(set) var currentPlaneAnchor: ARPlaneAnchor? { + didSet { + if (oldValue == nil && self.currentPlaneAnchor == nil) || (currentPlaneAnchor == oldValue) { + return + } + self.delegate?.focusEntity(self, planeChanged: currentPlaneAnchor, oldPlane: oldValue) + } + } /// The focus square's most recent alignments. internal var recentFocusEntityAlignments: [ARPlaneAnchor.Alignment] = [] From facef351e12b960d14815973319a679601f8ccd7 Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Mon, 9 May 2022 10:20:56 +0100 Subject: [PATCH 4/7] ignore comments for swiftlint line length --- .swiftlint.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.swiftlint.yml b/.swiftlint.yml index b5188da..6080b3e 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -4,3 +4,5 @@ disabled_rules: identifier_name: min_length: warning: 1 +line_length: + ignores_comments: true From b5497312da524735df4f963b22a317f22f254a9d Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Tue, 18 Oct 2022 14:42:46 +0100 Subject: [PATCH 5/7] added more docs, and docc overview --- .../FocusEntity/Documentation.docc/FocusEntity.md | 11 +++++++++++ Sources/FocusEntity/FocusEntity.swift | 13 +++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 Sources/FocusEntity/Documentation.docc/FocusEntity.md diff --git a/Sources/FocusEntity/Documentation.docc/FocusEntity.md b/Sources/FocusEntity/Documentation.docc/FocusEntity.md new file mode 100644 index 0000000..b289f9e --- /dev/null +++ b/Sources/FocusEntity/Documentation.docc/FocusEntity.md @@ -0,0 +1,11 @@ +# ``FocusEntity`` + +Visualise the camera focus in Augmented Reality. + +## Overview + +FocusEntity lets you see exactly where the centre of the view will sit in the AR space. To add FocusEntity to your scene: + +```swift +let focusSquare = FocusEntity(on: <#ARView#>, focus: .classic) +``` diff --git a/Sources/FocusEntity/FocusEntity.swift b/Sources/FocusEntity/FocusEntity.swift index cc57ec8..1eeb0d3 100644 --- a/Sources/FocusEntity/FocusEntity.swift +++ b/Sources/FocusEntity/FocusEntity.swift @@ -125,6 +125,8 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { private var updateCancellable: Cancellable? public private(set) var isAutoUpdating: Bool = false + /// Auto update the focus entity using `SceneEvents.Update`. + /// - Parameter autoUpdate: Should update the entity or not. public func setAutoUpdate(to autoUpdate: Bool) { guard autoUpdate != self.isAutoUpdating, !(autoUpdate && self.arView == nil) @@ -168,6 +170,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { } #endif + /// Current state of ``FocusEntity``. public var state: State = .initializing { didSet { guard state != oldValue else { return } @@ -200,6 +203,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { } } + /// Whether FocusEntity is on a plane or not. public internal(set) var onPlane: Bool = false /// Indicates if the square is currently being animated. @@ -250,9 +254,18 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { // MARK: - Initialization + /// Create a new ``FocusEntity`` instance. + /// - Parameters: + /// - arView: ARView containing the scene where the FocusEntity should be added. + /// - style: Style of the ``FocusEntity``. public convenience init(on arView: ARView, style: FocusEntityComponent.Style) { self.init(on: arView, focus: FocusEntityComponent(style: style)) } + + /// Create a new ``FocusEntity`` instance using the full ``FocusEntityComponent`` object. + /// - Parameters: + /// - arView: ARView containing the scene where the FocusEntity should be added. + /// - focus: Main component for the ``FocusEntity`` public required init(on arView: ARView, focus: FocusEntityComponent) { self.arView = arView super.init() From 9afe6a7391e68a66c42dc51143a3217d7d9fef24 Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Tue, 18 Oct 2022 14:45:06 +0100 Subject: [PATCH 6/7] added deploy docs github action --- .github/workflows/deploy_docs.yml | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/deploy_docs.yml diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml new file mode 100644 index 0000000..a18d92e --- /dev/null +++ b/.github/workflows/deploy_docs.yml @@ -0,0 +1,43 @@ +name: Deploy DocC + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: macos-12 + steps: + - name: Checkout ๐Ÿ›Ž๏ธ + uses: actions/checkout@v3 + - name: Build DocC ๐Ÿ›  + run: | + xcodebuild docbuild -scheme FocusEntity -derivedDataPath /tmp/docbuild -destination 'generic/platform=iOS'; + $(xcrun --find docc) process-archive \ + transform-for-static-hosting /tmp/docbuild/Build/Products/Debug-iphoneos/FocusEntity.doccarchive \ + --output-path docs \ + --hosting-base-path FocusEntity; + echo "" > docs/index.html + - name: Upload artifact ๐Ÿ“œ + uses: actions/upload-pages-artifact@v1 + with: + # Upload docs directory + path: 'docs' + - name: Deploy to GitHub Pages ๐Ÿ™ + id: deployment + uses: actions/deploy-pages@v1 \ No newline at end of file From 9d83ea63b0314b68f02ba0884d589a28a4093a3c Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Mon, 31 Oct 2022 10:40:34 +0000 Subject: [PATCH 7/7] merged workflow files, swiftlint fixed focusentity.swift --- .github/workflows/swift-build-lint.yml | 23 +++++++++++++++++++++++ .github/workflows/swift-build.yml | 25 ------------------------- .github/workflows/swift-lint.yml | 19 ------------------- Sources/FocusEntity/FocusEntity.swift | 24 ++++-------------------- 4 files changed, 27 insertions(+), 64 deletions(-) create mode 100644 .github/workflows/swift-build-lint.yml delete mode 100644 .github/workflows/swift-build.yml delete mode 100644 .github/workflows/swift-lint.yml diff --git a/.github/workflows/swift-build-lint.yml b/.github/workflows/swift-build-lint.yml new file mode 100644 index 0000000..61c34fd --- /dev/null +++ b/.github/workflows/swift-build-lint.yml @@ -0,0 +1,23 @@ +name: swiftlint + +on: + push: + branches: + - "main" + pull_request: + branches: + - "*" + +jobs: + build: + runs-on: macos-latest + steps: + - name: Checkout ๐Ÿ›Ž + uses: actions/checkout@v3 + - name: Swift Lint ๐Ÿงน + run: swiftlint --strict + - name: Test Build ๐Ÿ”จ + run: xcodebuild -scheme $SCHEME -destination $DESTINATION + env: + SCHEME: FocusEntity + DESTINATION: generic/platform=iOS diff --git a/.github/workflows/swift-build.yml b/.github/workflows/swift-build.yml deleted file mode 100644 index 730a846..0000000 --- a/.github/workflows/swift-build.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: build - -on: - push: - branches: - - "main" - tags: - - "!*" - pull_request: - branches: - - "*" - -jobs: - build: - runs-on: macOS-latest - steps: - - uses: actions/checkout@v1 - - name: Build Package - run: | - sudo xcode-select --switch $DEVELOPER_DIR - xcodebuild -scheme $SCHEME -destination $DESTINATION | xcpretty - env: - DEVELOPER_DIR: /Applications/Xcode_13.0.app/Contents/Developer - SCHEME: FocusEntity - DESTINATION: generic/platform=iOS diff --git a/.github/workflows/swift-lint.yml b/.github/workflows/swift-lint.yml deleted file mode 100644 index 1048071..0000000 --- a/.github/workflows/swift-lint.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: swiftlint - -on: - push: - branches: - - "main" - pull_request: - branches: - - "*" - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: GitHub Action for SwiftLint - uses: norio-nomura/action-swiftlint@3.2.1 - with: - args: --strict diff --git a/Sources/FocusEntity/FocusEntity.swift b/Sources/FocusEntity/FocusEntity.swift index 1eeb0d3..22d29fb 100644 --- a/Sources/FocusEntity/FocusEntity.swift +++ b/Sources/FocusEntity/FocusEntity.swift @@ -88,14 +88,6 @@ public extension FocusEntityDelegate { */ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { - public enum FEError: Error { - case noScene - } - - private var myScene: Scene? { - self.arView?.scene - } - internal weak var arView: ARView? /// For moving the FocusEntity to a whole new ARView @@ -134,7 +126,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { self.updateCancellable?.cancel() if autoUpdate { #if canImport(ARKit) - self.updateCancellable = self.myScene?.subscribe( + self.updateCancellable = self.arView?.scene.subscribe( to: SceneEvents.Update.self, self.updateFocusEntity ) #endif @@ -205,10 +197,8 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { /// Whether FocusEntity is on a plane or not. public internal(set) var onPlane: Bool = false - /// Indicates if the square is currently being animated. public internal(set) var isAnimating = false - /// Indicates if the square is currently changing its alignment. public internal(set) var isChangingAlignment = false @@ -272,7 +262,6 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { self.focus = focus self.name = "FocusEntity" self.orientation = simd_quatf(angle: .pi / 2, axis: [1, 0, 0]) - self.addChild(self.positioningEntity) cameraAnchor = AnchorEntity(.camera) @@ -290,9 +279,8 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { self.fillPlane = fillPlane self.coloredStateChanged() case .classic: - guard let classicStyle = self.focus.classicStyle else { - return - } + guard let classicStyle = self.focus.classicStyle + else { return } self.setupClassic(classicStyle) } } @@ -306,7 +294,6 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { /// Hides the focus square. func hide() { self.isEnabled = false - // runAction(.fadeOut(duration: 0.5), forKey: "hide") } /// Displays the focus square parallel to the camera plane. @@ -320,7 +307,6 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { /// Places the focus entity in front of the camera instead of on a plane. private func putInFrontOfCamera() { - // Works better than arView.ray() let newPosition = cameraAnchor.convert(position: [0, 0, -1], to: nil) recentFocusEntityPositions.append(newPosition) @@ -375,9 +361,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { case .classic: if self.onPlane { self.onPlaneAnimation(newPlane: newPlane) - } else { - self.offPlaneAniation() - } + } else { self.offPlaneAniation() } } }