diff --git a/FocusEntity-Example/FocusEntity-Example.xcodeproj/project.pbxproj b/FocusEntity-Example/FocusEntity-Example.xcodeproj/project.pbxproj index bc6a1cb..aaf845b 100644 --- a/FocusEntity-Example/FocusEntity-Example.xcodeproj/project.pbxproj +++ b/FocusEntity-Example/FocusEntity-Example.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ F301E0DD231462AB0028AAF1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F301E0DB231462AB0028AAF1 /* LaunchScreen.storyboard */; }; F301E0E5231462D90028AAF1 /* FocusARView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F301E0E4231462D90028AAF1 /* FocusARView.swift */; }; F339DB65275013C700D9A2B2 /* FocusEntity in Frameworks */ = {isa = PBXBuildFile; productRef = F339DB64275013C700D9A2B2 /* FocusEntity */; }; + F385E0D229E74E97007CF478 /* BasicARView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F385E0D129E74E97007CF478 /* BasicARView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -26,6 +27,7 @@ F301E0DE231462AB0028AAF1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F301E0E4231462D90028AAF1 /* FocusARView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusARView.swift; sourceTree = ""; }; F339DB622750138A00D9A2B2 /* FocusEntity */ = {isa = PBXFileReference; lastKnownFileType = folder; name = FocusEntity; path = ..; sourceTree = ""; }; + F385E0D129E74E97007CF478 /* BasicARView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicARView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -63,6 +65,7 @@ children = ( F301E0D0231462A90028AAF1 /* AppDelegate.swift */, F301E0D2231462A90028AAF1 /* ContentView.swift */, + F385E0D129E74E97007CF478 /* BasicARView.swift */, F301E0E4231462D90028AAF1 /* FocusARView.swift */, F301E0D6231462AB0028AAF1 /* Assets.xcassets */, F301E0DB231462AB0028AAF1 /* LaunchScreen.storyboard */, @@ -171,6 +174,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F385E0D229E74E97007CF478 /* BasicARView.swift in Sources */, F301E0D3231462A90028AAF1 /* ContentView.swift in Sources */, F301E0D1231462A90028AAF1 /* AppDelegate.swift in Sources */, F301E0E5231462D90028AAF1 /* FocusARView.swift in Sources */, @@ -311,13 +315,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"FocusEntity-Example/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 278494H572; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "FocusEntity-Example/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + PRODUCT_BUNDLE_IDENTIFIER = uk.rocketar.focusentity.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -330,13 +335,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"FocusEntity-Example/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 278494H572; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "FocusEntity-Example/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + PRODUCT_BUNDLE_IDENTIFIER = uk.rocketar.focusentity.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/FocusEntity-Example/FocusEntity-Example/BasicARView.swift b/FocusEntity-Example/FocusEntity-Example/BasicARView.swift new file mode 100644 index 0000000..44aab85 --- /dev/null +++ b/FocusEntity-Example/FocusEntity-Example/BasicARView.swift @@ -0,0 +1,31 @@ +// +// BasicARView.swift +// FocusEntity-Example +// +// Created by Max Cobb on 12/04/2023. +// Copyright © 2023 Max Cobb. All rights reserved. +// + +import SwiftUI +import RealityKit +import FocusEntity +import ARKit + +struct BasicARView: UIViewRepresentable { + typealias UIViewType = ARView + func makeUIView(context: Context) -> ARView { + let arView = ARView(frame: .zero) + let arConfig = ARWorldTrackingConfiguration() + arConfig.planeDetection = [.horizontal, .vertical] + arView.session.run(arConfig) + _ = FocusEntity(on: arView, style: .classic()) + return arView + } + func updateUIView(_ uiView: ARView, context: Context) {} +} + +struct BasicARView_Previews: PreviewProvider { + static var previews: some View { + BasicARView() + } +} diff --git a/FocusEntity-Example/FocusEntity-Example/ContentView.swift b/FocusEntity-Example/FocusEntity-Example/ContentView.swift index 0b3a196..2cabc57 100644 --- a/FocusEntity-Example/FocusEntity-Example/ContentView.swift +++ b/FocusEntity-Example/FocusEntity-Example/ContentView.swift @@ -11,7 +11,9 @@ import RealityKit struct ContentView: View { var body: some View { - ARViewContainer().edgesIgnoringSafeArea(.all) + BasicARView().edgesIgnoringSafeArea(.all) + // Uncomment the next line for a more complex example +// ARViewContainer().edgesIgnoringSafeArea(.all) } } diff --git a/Package.swift b/Package.swift index 74b78ed..2dce80e 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.5 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/Sources/FocusEntity/Documentation.docc/FocusEntity.md b/Sources/FocusEntity/Documentation.docc/FocusEntity.md deleted file mode 100644 index b289f9e..0000000 --- a/Sources/FocusEntity/Documentation.docc/FocusEntity.md +++ /dev/null @@ -1,11 +0,0 @@ -# ``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+Classic.swift b/Sources/FocusEntity/FocusEntity+Classic.swift index ab4a274..bc1293c 100644 --- a/Sources/FocusEntity/FocusEntity+Classic.swift +++ b/Sources/FocusEntity/FocusEntity+Classic.swift @@ -25,8 +25,6 @@ internal extension FocusEntity { /// Duration of the open/close animation. Not currently used. static let animationDuration = 0.7 - /// List of the segments in the focus square. - // MARK: - Initialization func setupClassic(_ classicStyle: ClassicStyle) { diff --git a/Sources/FocusEntity/FocusEntity.docc/FocusEntity.md b/Sources/FocusEntity/FocusEntity.docc/FocusEntity.md new file mode 100644 index 0000000..0f7cfcd --- /dev/null +++ b/Sources/FocusEntity/FocusEntity.docc/FocusEntity.md @@ -0,0 +1,42 @@ +# ``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) +``` + +To make a whole SwiftUI View with a FocusEntity: + +```swift +struct BasicARView: UIViewRepresentable { + typealias UIViewType = ARView + func makeUIView(context: Context) -> ARView { + let arView = ARView(frame: .zero) + let arConfig = ARWorldTrackingConfiguration() + arConfig.planeDetection = [.horizontal, .vertical] + arView.session.run(arConfig) + _ = FocusEntity(on: arView, style: .classic()) + return arView + } + func updateUIView(_ uiView: ARView, context: Context) {} +} +``` + +## Topics + +### FocusEntity + +- ``FocusEntity/FocusEntity`` +- ``FocusEntityComponent`` +- ``HasFocusEntity`` + +### Events + +Use the ``FocusEntityDelegate`` to catch events such as changing the plane anchor or otherwise a change of state. + +- ``FocusEntityDelegate`` diff --git a/Sources/FocusEntity/FocusEntity.swift b/Sources/FocusEntity/FocusEntity.swift index 22d29fb..a143425 100644 --- a/Sources/FocusEntity/FocusEntity.swift +++ b/Sources/FocusEntity/FocusEntity.swift @@ -46,9 +46,13 @@ public extension HasFocusEntity { public protocol FocusEntityDelegate: AnyObject { /// Called when the FocusEntity is now in world space + /// *Deprecated*: use ``focusEntity(_:trackingUpdated:oldState:)-4wx6e`` instead. + @available(*, deprecated, message: "use focusEntity(_:trackingUpdated:oldState:) instead") func toTrackingState() /// Called when the FocusEntity is tracking the camera + /// *Deprecated*: use ``focusEntity(_:trackingUpdated:oldState:)-4wx6e`` instead. + @available(*, deprecated, message: "use focusEntity(_:trackingUpdated:oldState:) instead") func toInitializingState() /// When the tracking state of the FocusEntity updates. This will be called every update frame. @@ -59,7 +63,7 @@ public protocol FocusEntityDelegate: AnyObject { func focusEntity( _ focusEntity: FocusEntity, trackingUpdated trackingState: FocusEntity.State, - oldState: 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. @@ -78,7 +82,7 @@ public extension FocusEntityDelegate { func toTrackingState() {} func toInitializingState() {} func focusEntity( - _ focusEntity: FocusEntity, trackingUpdated trackingState: FocusEntity.State, oldState: FocusEntity.State + _ focusEntity: FocusEntity, trackingUpdated trackingState: FocusEntity.State, oldState: FocusEntity.State? = nil ) {} func focusEntity(_ focusEntity: FocusEntity, planeChanged: ARPlaneAnchor?, oldPlane: ARPlaneAnchor?) {} } @@ -171,7 +175,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { case .initializing: if oldValue != .initializing { displayAsBillboard() - self.delegate?.toInitializingState() + self.delegate?.focusEntity(self, trackingUpdated: state, oldState: oldValue) } #if canImport(ARKit) case let .tracking(raycastResult, camera): @@ -185,13 +189,34 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { } else { entityOffPlane(raycastResult, camera) } + if self.scaleEntityBasedOnDistance, + let cameraTransform = self.arView?.cameraTransform { + self.scale = .one * scaleBasedOnDistance(cameraTransform: cameraTransform) + print(self.scale.x) + } + defer { currentPlaneAnchor = planeAnchor } if stateChanged { - self.delegate?.toTrackingState() + self.delegate?.focusEntity(self, trackingUpdated: state, oldState: oldValue) } #endif } - self.delegate?.focusEntity(self, trackingUpdated: state, oldState: oldValue) + } + } + + /** + Reduce visual size change with distance by scaling up when close and down when far away. + + These adjustments result in a scale of 1.0x for a distance of 0.7 m or less + (estimated distance when looking at a table), and a scale of 1.2x + for a distance 1.5 m distance (estimated distance when looking at the floor). + */ + private func scaleBasedOnDistance(cameraTransform: Transform) -> Float { + let distanceFromCamera = simd_length(self.position(relativeTo: nil) - cameraTransform.translation) + if distanceFromCamera < 0.7 { + return distanceFromCamera / 0.7 + } else { + return 0.25 * distanceFromCamera + 0.825 } } @@ -221,26 +246,17 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { /// 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() - internal var fillPlane: ModelEntity? - public var scaleEntityBasedOnDistance = true { - didSet { - if self.scaleEntityBasedOnDistance == false { - self.scale = .one - } - } - } + /// Modify the scale of the FocusEntity to make it slightly bigger when further away. + public var scaleEntityBasedOnDistance = true // MARK: - Initialization @@ -269,7 +285,7 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { // Start the focus square as a billboard. displayAsBillboard() - self.delegate?.toInitializingState() + self.delegate?.focusEntity(self, trackingUpdated: .initializing, oldState: nil) arView.scene.addAnchor(self) self.setAutoUpdate(to: true) switch self.focus.style { @@ -291,11 +307,6 @@ open class FocusEntity: Entity, HasAnchoring, HasFocusEntity { // MARK: - Appearance - /// Hides the focus square. - func hide() { - self.isEnabled = false - } - /// Displays the focus square parallel to the camera plane. private func displayAsBillboard() { self.onPlane = false