├── .gitignore ├── Apple's Interaction guidelines.md ├── Bridging-Header.h ├── BuddyBuildSDK.framework ├── BuddyBuildSDK ├── Headers │ └── BuddyBuildSDK.h ├── Modules │ └── module.modulemap └── build.num ├── Configuration └── SampleCode.xcconfig ├── Documentation ├── FocusSquareFigure.png └── StatusViewController.png ├── FileDescriptions.md ├── LICENSE └── LICENSE.txt ├── README.md ├── SwiftARlps.xcodeproj ├── .xcodesamplecode.plist ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── SwiftARlps.xcscheme └── SwiftARlps ├── Additional View Controllers ├── StatusViewController.swift └── VirtualObjectSelection.swift ├── AppDelegate.swift ├── Base.lproj └── Main.storyboard ├── Focus Square ├── FocusSquare.swift └── FocusSquareSegment.swift ├── Resources ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── AR_icon_3D copy-1.png │ │ ├── AR_icon_3D copy-10.png │ │ ├── AR_icon_3D copy-11.png │ │ ├── AR_icon_3D copy-12.png │ │ ├── AR_icon_3D copy-13.png │ │ ├── AR_icon_3D copy-2.png │ │ ├── AR_icon_3D copy-3.png │ │ ├── AR_icon_3D copy-4.png │ │ ├── AR_icon_3D copy-5.png │ │ ├── AR_icon_3D copy-6.png │ │ ├── AR_icon_3D copy-7.png │ │ ├── AR_icon_3D copy-8.png │ │ ├── AR_icon_3D copy-9.png │ │ ├── AR_icon_3D copy.png │ │ ├── AR_icon_3D.png │ │ └── Contents.json │ ├── Contents.json │ ├── add.imageset │ │ ├── Contents.json │ │ ├── add@2x.png │ │ └── add@3x.png │ ├── addPressed.imageset │ │ ├── Contents.json │ │ ├── addPressed@2x.png │ │ └── addpressed@3x.png │ ├── buttonring.imageset │ │ ├── Contents.json │ │ ├── ring@2x.png │ │ └── ring@3x.png │ ├── candle.imageset │ │ ├── Contents.json │ │ ├── candle@2x.png │ │ └── candle@3x.png │ ├── chair.imageset │ │ ├── Contents.json │ │ ├── chair@2x.png │ │ └── chair@3x.png │ ├── cup.imageset │ │ ├── Contents.json │ │ ├── cup@2x.png │ │ └── cup@3x.png │ ├── lamp.imageset │ │ ├── Contents.json │ │ ├── tableLamp@2x.png │ │ └── tableLamp@3x.png │ ├── polish.imageset │ │ ├── 750polish-1.png │ │ ├── 750polish-2.png │ │ ├── 750polish.png │ │ └── Contents.json │ ├── restart.imageset │ │ ├── Contents.json │ │ ├── refresh@2x.png │ │ └── refresh@3x.png │ ├── restartPressed.imageset │ │ ├── Contents.json │ │ ├── refreshPressed@2x.png │ │ └── refreshPressed@3x.png │ ├── shutter.imageset │ │ ├── Contents.json │ │ ├── shutter@2x.png │ │ └── shutter@3x.png │ ├── shutterPressed.imageset │ │ ├── Contents.json │ │ ├── shutterPressed@2px.png │ │ └── shutterPressed@3x.png │ ├── sweden.imageset │ │ ├── 750swedish-1.jpg │ │ ├── 750swedish-2.jpg │ │ ├── 750swedish.jpg │ │ └── Contents.json │ ├── swizz.imageset │ │ ├── 750swiss-1.png │ │ ├── 750swiss-2.png │ │ ├── 750swiss.png │ │ └── Contents.json │ └── vase.imageset │ │ ├── Contents.json │ │ ├── vase@2x.png │ │ └── vase@3x.png ├── BallMaterial.png ├── Default-568h@2x.png ├── Icons.xcassets │ ├── Contents.json │ ├── alert-circle.imageset │ │ ├── Contents.json │ │ └── alert-circle.pdf │ ├── alert-octagon.imageset │ │ ├── Contents.json │ │ └── alert-octagon.pdf │ ├── alert-triangle.imageset │ │ ├── Contents.json │ │ └── alert-triangle.pdf │ ├── anchor.imageset │ │ ├── Contents.json │ │ └── anchor.pdf │ ├── aperture.imageset │ │ ├── Contents.json │ │ └── aperture.pdf │ ├── arrow-down-left.imageset │ │ ├── Contents.json │ │ └── arrow-down-left.pdf │ ├── arrow-down-right.imageset │ │ ├── Contents.json │ │ └── arrow-down-right.pdf │ ├── arrow-down.imageset │ │ ├── Contents.json │ │ └── arrow-down.pdf │ ├── arrow-left.imageset │ │ ├── Contents.json │ │ └── arrow-left.pdf │ ├── arrow-right.imageset │ │ ├── Contents.json │ │ └── arrow-right.pdf │ ├── arrow-up-left.imageset │ │ ├── Contents.json │ │ └── arrow-up-left.pdf │ ├── arrow-up-right.imageset │ │ ├── Contents.json │ │ └── arrow-up-right.pdf │ ├── arrow-up.imageset │ │ ├── Contents.json │ │ └── arrow-up.pdf │ ├── award.imageset │ │ ├── Contents.json │ │ └── award.pdf │ ├── bookmark.imageset │ │ ├── Contents.json │ │ └── bookmark.pdf │ ├── box.imageset │ │ ├── Contents.json │ │ └── box.pdf │ ├── circle.imageset │ │ ├── Contents.json │ │ └── circle.pdf │ ├── clipboard.imageset │ │ ├── Contents.json │ │ └── clipboard.pdf │ ├── cloud-snow.imageset │ │ ├── Contents.json │ │ └── cloud-snow.pdf │ ├── codepen.imageset │ │ ├── Contents.json │ │ └── codepen.pdf │ ├── copy.imageset │ │ ├── Contents.json │ │ └── copy.pdf │ ├── crop.imageset │ │ ├── Contents.json │ │ └── crop.pdf │ ├── crosshair.imageset │ │ ├── Contents.json │ │ └── crosshair.pdf │ ├── droplet.imageset │ │ ├── Contents.json │ │ └── droplet.pdf │ ├── grid.imageset │ │ ├── Contents.json │ │ └── grid.pdf │ ├── heart.imageset │ │ ├── Contents.json │ │ └── heart.pdf │ ├── help-circle.imageset │ │ ├── Contents.json │ │ └── help-circle.pdf │ ├── layers.imageset │ │ ├── Contents.json │ │ └── layers.pdf │ ├── lock.imageset │ │ ├── Contents.json │ │ └── lock.pdf │ ├── maximize.imageset │ │ ├── Contents.json │ │ └── maximize.pdf │ ├── minus-circle.imageset │ │ ├── Contents.json │ │ └── minus-circle.pdf │ ├── more-horizontal.imageset │ │ ├── Contents.json │ │ └── more-horizontal.pdf │ ├── more-vertical.imageset │ │ ├── Contents.json │ │ └── more-vertical.pdf │ ├── move.imageset │ │ ├── Contents.json │ │ └── move.pdf │ ├── octagon.imageset │ │ ├── Contents.json │ │ └── octagon.pdf │ ├── package.imageset │ │ ├── Contents.json │ │ └── package.pdf │ ├── paperclip.imageset │ │ ├── Contents.json │ │ └── paperclip.pdf │ ├── plus-circle.imageset │ │ ├── Contents.json │ │ └── plus-circle.pdf │ ├── rotate-ccw.imageset │ │ ├── Contents.json │ │ └── rotate-ccw.pdf │ ├── rotate-cw.imageset │ │ ├── Contents.json │ │ └── rotate-cw.pdf │ ├── settings.imageset │ │ ├── Contents.json │ │ └── settings.pdf │ ├── shield.imageset │ │ ├── Contents.json │ │ └── shield.pdf │ ├── shuffle.imageset │ │ ├── Contents.json │ │ └── shuffle.pdf │ ├── sliders.imageset │ │ ├── Contents.json │ │ └── sliders.pdf │ ├── star.imageset │ │ ├── Contents.json │ │ └── star.pdf │ ├── tag.imageset │ │ ├── Contents.json │ │ └── tag.pdf │ ├── target.imageset │ │ ├── Contents.json │ │ └── target.pdf │ ├── trash-2.imageset │ │ ├── Contents.json │ │ └── trash-2.pdf │ ├── triangle.imageset │ │ ├── Contents.json │ │ └── triangle.pdf │ ├── wind.imageset │ │ ├── Contents.json │ │ └── wind.pdf │ ├── zap.imageset │ │ ├── Contents.json │ │ └── zap.pdf │ ├── zoom-in.imageset │ │ ├── Contents.json │ │ └── zoom-in.pdf │ └── zoom-out.imageset │ │ ├── Contents.json │ │ └── zoom-out.pdf ├── Info.plist ├── LaunchScreen.storyboard └── Models.scnassets │ ├── candle │ ├── candle.scn │ └── textures │ │ ├── candle_AO.png │ │ ├── candle_DIFFUSE.png │ │ ├── candle_METALLIC.png │ │ ├── candle_NORMAL.png │ │ ├── candle_ROUGHNESS.png │ │ ├── candle_SHADOW.png │ │ └── flame_PARTICLE.png │ ├── chair │ ├── chair.scn │ └── textures │ │ ├── chair_DIFFUSE.png │ │ ├── chair_METALLIC.png │ │ ├── chair_NORMAL.png │ │ ├── chair_ROUGHNESS.png │ │ └── chair_SHADOW.png │ ├── cup │ ├── cup.scn │ └── textures │ │ ├── cup_AO.png │ │ ├── cup_DIFFUSE.png │ │ ├── cup_METALLIC.png │ │ ├── cup_NORMAL.png │ │ ├── cup_ROUGHNESS.png │ │ ├── cup_SHADOW.png │ │ └── cup_steam_SPRITE.png │ ├── lamp │ ├── lamp.scn │ └── textures │ │ ├── lamp_DIFFUSE.png │ │ ├── lamp_METALLIC.png │ │ ├── lamp_NORMAL.png │ │ ├── lamp_ROUGHNESS.png │ │ └── lamp_SHADOW.png │ ├── sharedImages │ ├── environment.jpg │ └── environment_blur.exr │ └── vase │ ├── textures │ ├── vase_AO.png │ ├── vase_DIFFUSE.png │ ├── vase_METALLIC.png │ ├── vase_NORMAL.png │ ├── vase_ROUGHNESS.png │ ├── vase_SHADOW.png │ └── water_DIFFUSE.png │ └── vase.scn ├── SCNNode+Extensions.swift ├── ThresholdPanGesture.swift ├── Utilities └── Utilities.swift ├── ViewController+ARSCNViewDelegate.swift ├── ViewController+Actions.swift ├── ViewController+ObjectSelection.swift ├── ViewController.swift ├── Virtual Objects ├── Ball.swift ├── BallCube.swift ├── Cube.swift ├── ModelObject.swift ├── Plane.swift ├── Positionable.swift ├── VirtualObject.swift └── VirtualObjectList.swift ├── VirtualObjectARView.swift └── VirtualObjectInteraction.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # See LICENSE folder for this sample’s licensing information. 2 | # 3 | # Apple sample code gitignore configuration. 4 | 5 | # Finder 6 | .DS_Store 7 | 8 | # Xcode - User files 9 | xcuserdata/ 10 | *.xcworkspace 11 | -------------------------------------------------------------------------------- /Apple's Interaction guidelines.md: -------------------------------------------------------------------------------- 1 | # Handling 3D Interaction and UI Controls in Augmented Reality 2 | 3 | Follow best practices for visual feedback, gesture interactions, and realistic rendering in AR experiences. 4 | 5 | ## Overview 6 | 7 | Augmented reality (AR) offers new ways for users to interact with real and virtual 3D content in your app. However, many fundamental principles of human interface design are still valid. Convincing AR illusions also require careful attention to 3D asset design and rendering. The [iOS Human Interface Guidelines][0] include advice on human interface principles for AR. This project shows ways to apply those guidelines and easily create immersive, intuitive AR experiences. 8 | 9 | [0]:https://developer.apple.com/ios/human-interface-guidelines/technologies/augmented-reality/ 10 | 11 | This sample app provides a simple AR experience allowing a user to place one or more realistic virtual objects in their real-world environment, then arrange those objects using intuitive gestures. The app offers user interface cues to help the user understand the state of the AR experience and their options for interaction. 12 | 13 | The sections below correspond to sections in [iOS Human Interface Guidelines > Augmented Reality][0], and provide details on how this sample app implements those guidelines. For more detailed reasoning on each section, see the corresponding content in the iOS Human Interface Guidelines. 14 | 15 | ## Getting Started 16 | 17 | ARKit and this sample app require iOS 11 and a device with an A9 (or later) processor. ARKit is not available in iOS Simulator. 18 | 19 | ## Placing Virtual Objects 20 | 21 | **Help people understand when to locate a surface and place an object.** 22 | The [`FocusSquare`](x-source-tag://FocusSquare) class draws a square outline in the AR view, giving the user hints about the status of ARKit world tracking. 23 | 24 | ![Focus square UI before and after plane detection, disappearing after object selected for placement](Documentation/FocusSquareFigure.png) 25 | 26 | The square changes size and orientation to reflect estimated scene depth, and switches between open and closed states with a prominent animation to indicate whether ARKit has detected a plane suitable for placing an object. After the user places a virtual object, the focus square disappears, remaining hidden until the user points the camera at another surface. 27 | 28 | **Respond appropriately when the user places an object.** 29 | When the user chooses a virtual object to place, the sample app's [`setPosition(_:relativeTo:smoothMovement)`](x-source-tag://VirtualObjectSetPosition) method uses the [`FocusSquare`](x-source-tag://FocusSquare) object's simple heuristics to place the object at a roughly realistic position in the middle of the screen, even if ARKit hasn't yet detected a plane at that location. 30 | 31 | ``` swift 32 | guard let cameraTransform = session.currentFrame?.camera.transform, 33 | let focusSquarePosition = focusSquare.lastPosition else { 34 | statusViewController.showMessage("CANNOT PLACE OBJECT\nTry moving left or right.") 35 | return 36 | } 37 | 38 | virtualObjectInteraction.selectedObject = virtualObject 39 | virtualObject.setPosition(focusSquarePosition, relativeTo: cameraTransform, smoothMovement: false) 40 | 41 | updateQueue.async { 42 | self.sceneView.scene.rootNode.addChildNode(virtualObject) 43 | } 44 | ``` 45 | [View in Source](x-source-tag://PlaceVirtualObject) 46 | 47 | This position might not be an accurate estimate of the real-world surface the user wants to place the virtual object on, but it's close enough to get the object onscreen quickly. 48 | 49 | Over time, ARKit detects planes and refines its estimates of their position, calling the [`renderer(_:didAdd:for:)`][4] and [`renderer(_:didUpdate:for:)`][5] delegate methods to report results. In those methods, the sample app calls its [`adjustOntoPlaneAnchor(_:using:)`](x-source-tag://AdjustOntoPlaneAnchor) method to determine whether a previously placed virtual object is close to a detected plane. If so, that method uses a subtle animation to move the virtual object onto the plane, so that the object appears to be at the user's chosen position while benefiting from ARKit's refined estimate of the real-world surface at that position: 50 | 51 | ``` swift 52 | // Move onto the plane if it is near it (within 5 centimeters). 53 | let verticalAllowance: Float = 0.05 54 | let epsilon: Float = 0.001 // Do not update if the difference is less than 1 mm. 55 | let distanceToPlane = abs(planePosition.y) 56 | if distanceToPlane > epsilon && distanceToPlane < verticalAllowance { 57 | SCNTransaction.begin() 58 | SCNTransaction.animationDuration = CFTimeInterval(distanceToPlane * 500) // Move 2 mm per second. 59 | SCNTransaction.animationTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 60 | position.y = anchor.transform.columns.3.y 61 | SCNTransaction.commit() 62 | } 63 | ``` 64 | [View in Source](x-source-tag://AdjustOntoPlaneAnchor) 65 | 66 | [4]:https://developer.apple.com/documentation/arkit/arscnviewdelegate/2865794-renderer 67 | [5]:https://developer.apple.com/documentation/arkit/arscnviewdelegate/2865799-renderer 68 | 69 | ## User Interaction with Virtual Objects 70 | 71 | **Allow people to directly interact with virtual objects using standard, familiar gestures.** 72 | The sample app uses one-finger tap, one- and two-finger pan, and two-finger rotation gesture recognizers to let the user position and orient virtual objects. The sample code's [`VirtualObjectInteraction`](x-source-tag://VirtualObjectInteraction) class manages these gestures. 73 | 74 | **In general, keep interactions simple.** 75 | When dragging a virtual object (see the [`translate(_:basedOn:infinitePlane:)`](x-source-tag://DragVirtualObject) method), the sample app restricts the object's movement to the two-dimensional plane it's placed on. Similarly, because a virtual object rests on a horizontal plane, rotation gestures (see the [`didRotate(_:)`](x-source-tag://didRotate) method) spin the object around its vertical axis only, so that the object remains on the plane. 76 | 77 | **Respond to gestures within reasonable proximity of interactive virtual objects.** 78 | The sample code's [`objectInteracting(with:in:)`](x-source-tag://TouchTesting) method performs hit tests using the touch locations provided by gesture recognizers. By hit testing against the bounding boxes of the virtual objects, the method makes it more likely that a user touch will affect the object even if the touch location isn't on a point where the object has visible content. By performing multiple hit tests for multitouch gestures, the method makes it more likely that the user touch affects the intended object: 79 | 80 | ``` swift 81 | for index in 0.., with event: UIEvent) { 103 | super.touchesMoved(touches, with: event) 104 | 105 | let translationMagnitude = translation(in: view).length 106 | 107 | // Adjust the threshold based on the number of touches being used. 108 | let threshold = ThresholdPanGesture.threshold(forTouchCount: touches.count) 109 | 110 | if !isThresholdExceeded && translationMagnitude > threshold { 111 | isThresholdExceeded = true 112 | 113 | // Set the overall translation to zero as the gesture should now begin. 114 | setTranslation(.zero, in: view) 115 | } 116 | } 117 | ``` 118 | [View in Source](x-source-tag://touchesMoved) 119 | 120 | [3]:https://developer.apple.com/documentation/uikit/uipangesturerecognizer 121 | 122 | **Make sure virtual object movements are smooth.** 123 | The sample code's [`setPosition(_:relativeTo:smoothMovement)`](x-source-tag://VirtualObjectSetPosition) method interpolates between the touch gesture locations that result in dragging an object and a history of that object's recent positions. By averaging recent positions based on distance to the camera, this method produces smooth dragging movement without causing the dragged object to lag behind the user's gesture: 124 | 125 | ``` swift 126 | if smoothMovement { 127 | let hitTestResultDistance = simd_length(positionOffsetFromCamera) 128 | 129 | // Add the latest position and keep up to 10 recent distances to smooth with. 130 | recentVirtualObjectDistances.append(hitTestResultDistance) 131 | recentVirtualObjectDistances = Array(recentVirtualObjectDistances.suffix(10)) 132 | 133 | let averageDistance = recentVirtualObjectDistances.average! 134 | let averagedDistancePosition = simd_normalize(positionOffsetFromCamera) * averageDistance 135 | simdPosition = cameraWorldPosition + averagedDistancePosition 136 | } else { 137 | simdPosition = cameraWorldPosition + positionOffsetFromCamera 138 | } 139 | ``` 140 | [View in Source](x-source-tag://VirtualObjectSetPosition) 141 | 142 | **Explore even more engaging methods of interaction.** 143 | In an AR experience, a pan gesture—that is, moving one's finger across the device's screen—isn't the only natural way to drag virtual content to a new position. A user might also intuitively try holding a finger still against the screen while moving the device, effectively dragging the touch point across the AR scene. 144 | 145 | The sample app supports this kind of gesture by calling its [`updateObjectToCurrentTrackingPosition()`](x-source-tag://updateObjectToCurrentTrackingPosition) method continually while a drag gesture is in progress, even if the gesture's touch location hasn't changed. If the device moves during a drag, that method calculates the new world position corresponding to the touch location and moves the virtual object accordingly. 146 | 147 | ## Entering Augmented Reality 148 | 149 | **Indicate when initialization is occurring and involve the user.** 150 | The sample app shows textual hints about the state of the AR session and instructions for interacting with the AR experience using a floating text view. The sample code's [`StatusViewController`](x-source-tag://StatusViewController) class manages this view, showing transient instructions that fade away after allowing the user time to read them, or important status messages that remain visible until the user corrects a problem. 151 | 152 | ![Status view for displaying information about the session state.](Documentation/StatusViewController.png) 153 | 154 | ## Handling Problems 155 | 156 | **Allow people to reset the experience if it doesn’t meet their expectations.** 157 | The sample app has a Reset button that's always visible in the upper-right corner of the UI, allowing a user to restart the AR experience regardless of its current state. See the [`restartExperience()`](x-source-tag://restartExperience) method in the sample code. 158 | 159 | **Offer AR features only on capable devices.** 160 | The sample app requires ARKit for its core functionality, so it defines the `arkit` key in the [`UIRequiredDeviceCapabilities`][1] section of its `Info.plist` file. When deploying the built project, this key prevents installation of the app on devices that don't support ARKit. 161 | 162 | If your app instead uses AR as a secondary feature, use the [`ARWorldTrackingConfiguration.isSupported`][2] method to determine whether to hide features that require ARKit. 163 | 164 | [1]:https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/iPhoneOSKeys.html#//apple_ref/doc/uid/TP40009252-SW3 165 | [2]:https://developer.apple.com/documentation/arkit/arconfiguration/2923553-issupported 166 | -------------------------------------------------------------------------------- /Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import 2 | -------------------------------------------------------------------------------- /BuddyBuildSDK.framework/BuddyBuildSDK: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/BuddyBuildSDK.framework/BuddyBuildSDK -------------------------------------------------------------------------------- /BuddyBuildSDK.framework/Headers/BuddyBuildSDK.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Doe Pics Hit, Inc. All rights reserved. 2 | 3 | #import 4 | #import 5 | 6 | typedef NSString*(^BBReturnNSStringCallback)(void); 7 | typedef BOOL (^BBReturnBooleanCallback)(void); 8 | typedef void (^BBCallback)(void); 9 | 10 | @interface BuddyBuildSDK : NSObject 11 | 12 | // Deprecated 13 | + (void)setup:(id)bbAppDelegate; 14 | 15 | /** 16 | * Initialize the SDK 17 | * 18 | * This should be called at (or near) the start of the appdelegate 19 | */ 20 | + (void)setup; 21 | 22 | /* 23 | * Associate arbitrary key/value pairs with your crash reports and user feedback 24 | * which will be visible from the buddybuild dashboard 25 | */ 26 | + (void)setMetadataObject:(id)object forKey:(NSString*)key; 27 | 28 | /* 29 | * Programatically trigger the screenshot feedback UI without pressing the screenshot buttons 30 | * If you have screenshot feedback disabled through the buddybuild setting, 31 | * you can still trigger it by calling this method 32 | */ 33 | 34 | + (void)takeScreenshotAndShowFeedbackScreen; 35 | 36 | /* 37 | * If you distribute a build to someone with their email address, buddybuild can 38 | * figure out who they are and attach their info to feedback and crash reports. 39 | * 40 | * However, if you send out a build to a mailing list, or through TestFlight or 41 | * the App Store we are unable to infer who they are. If you see 'Unknown User' 42 | * this is likely the cause. 43 | 44 | * Often you'll know the identity of your user, for example, after they've 45 | * logged in. You can provide buddybuild a callback to identify the current user. 46 | */ 47 | 48 | + (void)setUserDisplayNameCallback:(BBReturnNSStringCallback)bbCallback; 49 | 50 | /* 51 | * You might have API keys and other secrets that your app needs to consume. 52 | * However, you may not want to check these secrets into the source code. 53 | * 54 | * You can provide your secrets to buddybuild. Buddybuild can then expose them 55 | * to you at build time through environment variables. These secrets can also be 56 | * configured to be included into built app. We obfuscate the device keys to 57 | * prevent unauthorized access. 58 | */ 59 | + (NSString*)valueForDeviceKey:(NSString*)bbKey; 60 | 61 | /* 62 | * To temporarily disable screenshot interception you can provide a callback 63 | * here. 64 | * 65 | * When screenshotting is turned on through a buddybuild setting, and no 66 | * callback is provided then screenshotting is by default on. 67 | * 68 | * If screenshotting is disabled through the buddybuild setting, then this 69 | * callback has no effect 70 | * 71 | */ 72 | + (void)setScreenshotAllowedCallback:(BBReturnBooleanCallback)bbCallback; 73 | 74 | /* 75 | * Once a piece of feedback is sent this callback will be called 76 | * so you can take additional actions if necessary 77 | */ 78 | + (void)setScreenshotFeedbackSentCallback:(BBCallback)bbCallback; 79 | 80 | /* 81 | * Once a crash report is sent this callback will be called 82 | * so you can take additional actions if necessary 83 | */ 84 | + (void)setCrashReportSentCallback:(BBCallback)bbCallback; 85 | 86 | /* 87 | * Buddybuild Build Number 88 | */ 89 | + (NSString*)buildNumber; 90 | 91 | /* 92 | * Scheme 93 | */ 94 | + (NSString*)scheme; 95 | 96 | /* 97 | * App ID 98 | */ 99 | + (NSString*)appID; 100 | 101 | /* 102 | * Build ID 103 | */ 104 | + (NSString*)buildID; 105 | 106 | /* 107 | * Build Configuration 108 | */ 109 | 110 | + (NSString*)buildConfiguration; 111 | 112 | /* 113 | * Branch name for this build 114 | */ 115 | 116 | + (NSString*)branchName; 117 | 118 | /* 119 | * Returns the user's email or more specifically, the email that was used to download and deploy the build. 120 | * Returns "Unknown User" in cases where buddybuild is unable to identify the user. 121 | * This is the same email seen in crash instances and feedbacks in the dashboard 122 | * NOTE: To be called after [BuddyBuildSDK setup] 123 | * this is different than the one returned in the user display name callback. 124 | */ 125 | + (NSString*)userEmail; 126 | 127 | /* Manually invoke the screenshot tutorial 128 | * If you don't want it to appear on app launch, disable it in the 129 | * dashboard by going to settings -> buddybuildSDK -> Feature Settings and turning off the screenshot tutorial 130 | * You will be able to show it at any time from anywhere in your app 131 | */ 132 | + (void)showScreenshotTutorial; 133 | 134 | 135 | + (void)crash; 136 | 137 | /* 138 | * Logs to the console only while the debugger is attached (when running in Xcode) 139 | * They can be downloaded in crash instances and feedbacks in the dashboard 140 | */ 141 | + (void)log:(NSString *)message; 142 | 143 | /* 144 | * Starts recording video when running a UI test case. 145 | * Should be called after each "[[[XCUIApplication alloc] init] launch];" in your UI tests codebase. 146 | * Only run in buddybuild while the UI tests run. It will not run locally, on real iOS devices or on TestFlight and App Store installs. 147 | */ 148 | + (void)startUITests; 149 | 150 | /* 151 | * Stops recording video at the end of a UI test case. 152 | * Should be called before each "[super tearDown];" in your UI tests codebase. 153 | * Only run in buddybuild while the UI tests run. It will not run locally, on real iOS devices or on TestFlight and App Store installs. 154 | 155 | */ 156 | + (void)stopUITests; 157 | 158 | /* 159 | * Should be called in your app delegate in -[UIApplication application:didReceiveRemoteNotification:fetchCompletionHandler]. 160 | * Only run in buddybuild while the UI tests run. It will not run locally, on real iOS devices or on TestFlight and App Store installs. 161 | */ 162 | + (void)uiTestsDidReceiveRemoteNotification:(NSDictionary *)userInfo; 163 | 164 | /* 165 | * DEPRECATED IN SDK 1.0.16+, use setMetadataObject:forKey: 166 | */ 167 | + (void)setCrashMetadataObject:(id)object forKey:(NSString*)key __deprecated_msg("Use setMetadataObject:forKey: instead"); 168 | 169 | 170 | @end 171 | 172 | @interface UIView (BuddyBuildSDK) 173 | 174 | // Certain features of buddybuild involve capturing the screen (either through a static screenshot, or as a video for instant replays in crash reporting or video feedback. 175 | // Your app may contain certain sensitive customer information that you do not want to be included in the video. 176 | // If you set this property to be true, this view will be redacted from the screen capture and blacked out 177 | 178 | @property (nonatomic, assign) BOOL buddybuildViewIsPrivate; 179 | 180 | @end 181 | -------------------------------------------------------------------------------- /BuddyBuildSDK.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | module BuddyBuildSDK { 2 | umbrella header "Headers/BuddyBuildSDK.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /BuddyBuildSDK.framework/build.num: -------------------------------------------------------------------------------- 1 | 954d994 2 | -------------------------------------------------------------------------------- /Configuration/SampleCode.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // SampleCode.xcconfig 3 | // 4 | 5 | // The `SAMPLE_CODE_DISAMBIGUATOR` configuration is to make it easier to build 6 | // and run a sample code project. Once you set your project's development team, 7 | // you'll have a unique bundle identifier. This is because the bundle identifier 8 | // is derived based on the 'SAMPLE_CODE_DISAMBIGUATOR' value. Do not use this 9 | // approach in your own projects—it's only useful for sample code projects because 10 | // they are frequently downloaded and don't have a development team set. 11 | SAMPLE_CODE_DISAMBIGUATOR=${DEVELOPMENT_TEAM} 12 | -------------------------------------------------------------------------------- /Documentation/FocusSquareFigure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/Documentation/FocusSquareFigure.png -------------------------------------------------------------------------------- /Documentation/StatusViewController.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/Documentation/StatusViewController.png -------------------------------------------------------------------------------- /FileDescriptions.md: -------------------------------------------------------------------------------- 1 | ## ViewController.swift 2 | 3 | * Does the setup of the scene and it’s elements 4 | * Contains an `updateQueue` property on which the adding and deleting of nodes 5 | needs to be scheduled (automatic through `placeVirtualObject()`) 6 | 7 | ## VirtualObjectARView.swift 8 | 9 | * Subclass of `ARSCNView`, which comes with ARKit 10 | * Keeps the position in the real world synced with the position in the virtual 11 | world 12 | * Has some helper methods converting from screen to the virtual world or doing 13 | hit testing 14 | 15 | ## ViewController+Actions.swift 16 | 17 | * Handles the actions like adding objects 18 | * Is where you would put `@IBAction`s 19 | 20 | ## ViewController+ARSCNViewDelegate.swift 21 | 22 | * Contains the callbacks from ARKit into our app 23 | * Adjusts all the objects back to the detected plane 24 | 25 | ## ViewController+ObjectSelection.swift 26 | 27 | * Contains `func placeVirtualObject(_ virtualObject: VirtualObject)` 28 | * Handles the selecting of the build-in model objects 29 | 30 | ## VirtualObjectInteraction.swift 31 | 32 | * Handles all interaction with `VirtualObject`s 33 | * Contains all the Gesture Recognizers and the handling of them 34 | 35 | ## VirtualObjectList.swift 36 | 37 | * Manages all virtual objects in the scene 38 | 39 | ## VirtualObject.swift 40 | 41 | * Type of objects that we’re going to add to the scene 42 | * Combination of `SCNNode` and `Positionable` protocol 43 | 44 | ## Cube.swift 45 | 46 | * Example of a custom VirtualObject that can be added 47 | 48 | ## Main.storyboard 49 | 50 | * Set’s up the scene and UI 51 | * Try to modify as little as possible to avoid merge conflicts 52 | -------------------------------------------------------------------------------- /LICENSE/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2017 Apple Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![BuddyBuild](https://dashboard.buddybuild.com/api/statusImage?appID=5a13e4c292ae680001a82664&branch=master&build=latest)](https://dashboard.buddybuild.com/apps/5a13e4c292ae680001a82664/build/latest?branch=master) 2 | 3 | ## 🏃‍♀️ Getting started 4 | 5 | * Open `SwiftARlps.xcodeproj`. 6 | * Select a development team in the project settings. 7 | * Build and run on your device. 8 | 9 | Point your camera at a surface, wait until it detects a surface and then press 10 | one of the buttons to place an object. 11 | 12 | ## Process 13 | 14 | * 👩‍👦 Pair up with another attendee. 15 | * 🍴 Fork the repository, and optionally create a branch. 16 | * 💡 Think of a feature to add, you can come up with something yourself or 17 | choose from the [Issues](https://github.com/TheSwiftAlps/SwiftARlps/issues). 18 | * 🎯 Commit and push your work, and create a 19 | [Pull Request](https://github.com/TheSwiftAlps/SwiftARlps/pulls). 20 | 21 | ## 🦉 Tips 22 | 23 | * Start by looking at the FileDescriptions.md file for a description of what 24 | files have which responsibility. 25 | * Choose a simple feature to add, you can add more complexity later. 26 | * Don't worry about the UI too much, just try make something work. 27 | * The units of ARKit is in meters, so be sure you don't place something 200 away 28 | * There are a bunch of icons to choose from in `Icons.xcassets`. 29 | * If you want to show a message to the user, you can use the 30 | `StatusViewController` with a `showMessage(_: String, autoHide: Bool)` method. 31 | * Don't be afraid to remove some code if it blocks your current idea. 32 | * When adding kind of new objects, start by copy-pasting an existing subclass of 33 | `SCNNode` (like `Cube`) 34 | * When doing something with `VirtualObject`s see if you can add it as an 35 | extension to `SCNNode` 36 | 37 | ## 📚 Resources 38 | 39 | * [Handling 3D Interaction and UI Controls in Augmented Reality](https://developer.apple.com/documentation/arkit/handling_3d_interaction_and_ui_controls_in_augmented_reality) 40 | * [ARKit Framework](https://developer.apple.com/documentation/arkit) 41 | * [WWDC 2017 - Session 602, Introducing ARKit: Augmented Reality for iOS ](https://developer.apple.com/videos/play/wwdc2017/602/) 42 | * [AR Human Interface Guidelines](https://developer.apple.com/ios/human-interface-guidelines/technologies/augmented-reality/) 43 | * [Performing Spatial Transformations](https://www.toptal.com/javascript/3d-graphics-a-webgl-tutorial#performing-spatial-transformations) 44 | 45 | ## 💡 Inspiration 46 | 47 | * [Made With ARKit](http://www.madewitharkit.com) 48 | * [ARPaint](https://www.toptal.com/swift/ios-arkit-tutorial-drawing-in-air-with-fingers) 49 | ([Video](https://www.youtube.com/watch?v=gb9E0n8m5pE), 50 | [GitHub](https://github.com/oabdelkarim/ARPaint)) 51 | -------------------------------------------------------------------------------- /SwiftARlps.xcodeproj/.xcodesamplecode.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SwiftARlps.xcodeproj/xcshareddata/xcschemes/SwiftARlps.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /SwiftARlps/Additional View Controllers/StatusViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Utility class for showing messages above the AR view. 6 | */ 7 | 8 | import Foundation 9 | import ARKit 10 | 11 | /** 12 | Displayed at the top of the main interface of the app that allows users to see 13 | the status of the AR experience, as well as the ability to control restarting 14 | the experience altogether. 15 | - Tag: StatusViewController 16 | */ 17 | class StatusViewController: UIViewController { 18 | // MARK: - Types 19 | 20 | enum MessageType { 21 | case trackingStateEscalation 22 | case planeEstimation 23 | case contentPlacement 24 | case focusSquare 25 | 26 | static var all: [MessageType] = [ 27 | .trackingStateEscalation, 28 | .planeEstimation, 29 | .contentPlacement, 30 | .focusSquare 31 | ] 32 | } 33 | 34 | // MARK: - IBOutlets 35 | 36 | @IBOutlet weak private var messagePanel: UIVisualEffectView! 37 | 38 | @IBOutlet weak private var messageLabel: UILabel! 39 | 40 | @IBOutlet weak private var restartExperienceButton: UIButton! 41 | 42 | // MARK: - Properties 43 | 44 | /// Trigerred when the "Restart Experience" button is tapped. 45 | var restartExperienceHandler: () -> Void = {} 46 | 47 | /// Seconds before the timer message should fade out. Adjust if the app needs longer transient messages. 48 | private let displayDuration: TimeInterval = 6 49 | 50 | // Timer for hiding messages. 51 | private var messageHideTimer: Timer? 52 | 53 | private var timers: [MessageType: Timer] = [:] 54 | 55 | // MARK: - Message Handling 56 | 57 | func showMessage(_ text: String, autoHide: Bool = true) { 58 | // Cancel any previous hide timer. 59 | messageHideTimer?.invalidate() 60 | 61 | messageLabel.text = text 62 | 63 | // Make sure status is showing. 64 | setMessageHidden(false, animated: true) 65 | 66 | if autoHide { 67 | messageHideTimer = Timer.scheduledTimer(withTimeInterval: displayDuration, repeats: false, block: { [weak self] _ in 68 | self?.setMessageHidden(true, animated: true) 69 | }) 70 | } 71 | } 72 | 73 | func scheduleMessage(_ text: String, inSeconds seconds: TimeInterval, messageType: MessageType) { 74 | cancelScheduledMessage(for: messageType) 75 | 76 | let timer = Timer.scheduledTimer(withTimeInterval: seconds, repeats: false, block: { [weak self] timer in 77 | self?.showMessage(text) 78 | timer.invalidate() 79 | }) 80 | 81 | timers[messageType] = timer 82 | } 83 | 84 | func cancelScheduledMessage(`for` messageType: MessageType) { 85 | timers[messageType]?.invalidate() 86 | timers[messageType] = nil 87 | } 88 | 89 | func cancelAllScheduledMessages() { 90 | for messageType in MessageType.all { 91 | cancelScheduledMessage(for: messageType) 92 | } 93 | } 94 | 95 | // MARK: - ARKit 96 | 97 | func showTrackingQualityInfo(for trackingState: ARCamera.TrackingState, autoHide: Bool) { 98 | showMessage(trackingState.presentationString, autoHide: autoHide) 99 | } 100 | 101 | func escalateFeedback(for trackingState: ARCamera.TrackingState, inSeconds seconds: TimeInterval) { 102 | cancelScheduledMessage(for: .trackingStateEscalation) 103 | 104 | let timer = Timer.scheduledTimer(withTimeInterval: seconds, repeats: false, block: { [unowned self] _ in 105 | self.cancelScheduledMessage(for: .trackingStateEscalation) 106 | 107 | var message = trackingState.presentationString 108 | if let recommendation = trackingState.recommendation { 109 | message.append(": \(recommendation)") 110 | } 111 | 112 | self.showMessage(message, autoHide: false) 113 | }) 114 | 115 | timers[.trackingStateEscalation] = timer 116 | } 117 | 118 | // MARK: - IBActions 119 | 120 | @IBAction private func restartExperience(_ sender: UIButton) { 121 | restartExperienceHandler() 122 | } 123 | 124 | // MARK: - Panel Visibility 125 | 126 | private func setMessageHidden(_ hide: Bool, animated: Bool) { 127 | // The panel starts out hidden, so show it before animating opacity. 128 | messagePanel.isHidden = false 129 | 130 | guard animated else { 131 | messagePanel.alpha = hide ? 0 : 1 132 | return 133 | } 134 | 135 | UIView.animate(withDuration: 0.2, delay: 0, options: [.beginFromCurrentState], animations: { 136 | self.messagePanel.alpha = hide ? 0 : 1 137 | }, completion: nil) 138 | } 139 | } 140 | 141 | extension ARCamera.TrackingState { 142 | var presentationString: String { 143 | switch self { 144 | case .notAvailable: 145 | return "TRACKING UNAVAILABLE" 146 | case .normal: 147 | return "TRACKING NORMAL" 148 | case .limited(.excessiveMotion): 149 | return "TRACKING LIMITED\nExcessive motion" 150 | case .limited(.insufficientFeatures): 151 | return "TRACKING LIMITED\nLow detail" 152 | case .limited(.initializing): 153 | return "Initializing" 154 | } 155 | } 156 | 157 | var recommendation: String? { 158 | switch self { 159 | case .limited(.excessiveMotion): 160 | return "Try slowing down your movement, or reset the session." 161 | case .limited(.insufficientFeatures): 162 | return "Try pointing at a flat surface, or reset the session." 163 | default: 164 | return nil 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /SwiftARlps/Additional View Controllers/VirtualObjectSelection.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Popover view controller for choosing virtual objects to place in the AR scene. 6 | */ 7 | 8 | import UIKit 9 | 10 | // MARK: - ObjectCell 11 | 12 | class ObjectCell: UITableViewCell { 13 | static let reuseIdentifier = "ObjectCell" 14 | 15 | @IBOutlet weak var objectTitleLabel: UILabel! 16 | @IBOutlet weak var objectImageView: UIImageView! 17 | 18 | var modelName = "" { 19 | didSet { 20 | objectTitleLabel.text = modelName.capitalized 21 | objectImageView.image = UIImage(named: modelName) 22 | } 23 | } 24 | } 25 | 26 | // MARK: - ModelObjectSelectionViewControllerDelegate 27 | 28 | /// A protocol for reporting which objects have been selected. 29 | protocol ModelObjectSelectionViewControllerDelegate: class { 30 | func modelObjectSelectionViewController(_ selectionViewController: ModelObjectSelectionViewController, didSelectObject: ModelObject) 31 | func modelObjectSelectionViewController(_ selectionViewController: ModelObjectSelectionViewController, didDeselectObject: ModelObject) 32 | } 33 | 34 | /// A custom table view controller to allow users to select `ModelObject`s for placement in the scene. 35 | class ModelObjectSelectionViewController: UITableViewController { 36 | 37 | /// The collection of `ModelObject`s to select from. 38 | var modelObjects = [ModelObject]() 39 | 40 | /// The rows of the currently selected `ModelObject`s. 41 | var selectedModelObjectRows = IndexSet() 42 | 43 | weak var delegate: ModelObjectSelectionViewControllerDelegate? 44 | 45 | override func viewDidLoad() { 46 | super.viewDidLoad() 47 | 48 | tableView.separatorEffect = UIVibrancyEffect(blurEffect: UIBlurEffect(style: .light)) 49 | } 50 | 51 | override func viewWillLayoutSubviews() { 52 | preferredContentSize = CGSize(width: 250, height: tableView.contentSize.height) 53 | } 54 | 55 | // MARK: - UITableViewDelegate 56 | 57 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 58 | let object = modelObjects[indexPath.row] 59 | 60 | // Check if the current row is already selected, then deselect it. 61 | if selectedModelObjectRows.contains(indexPath.row) { 62 | delegate?.modelObjectSelectionViewController(self, didDeselectObject: object) 63 | } else { 64 | delegate?.modelObjectSelectionViewController(self, didSelectObject: object) 65 | } 66 | 67 | dismiss(animated: true, completion: nil) 68 | } 69 | 70 | // MARK: - UITableViewDataSource 71 | 72 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 73 | return modelObjects.count 74 | } 75 | 76 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 77 | guard let cell = tableView.dequeueReusableCell(withIdentifier: ObjectCell.reuseIdentifier, for: indexPath) as? ObjectCell else { 78 | fatalError("Expected `\(ObjectCell.self)` type for reuseIdentifier \(ObjectCell.reuseIdentifier). Check the configuration in Main.storyboard.") 79 | } 80 | 81 | cell.modelName = modelObjects[indexPath.row].modelName 82 | 83 | if selectedModelObjectRows.contains(indexPath.row) { 84 | cell.accessoryType = .checkmark 85 | } else { 86 | cell.accessoryType = .none 87 | } 88 | 89 | return cell 90 | } 91 | 92 | override func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) { 93 | let cell = tableView.cellForRow(at: indexPath) 94 | cell?.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5) 95 | } 96 | 97 | override func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) { 98 | let cell = tableView.cellForRow(at: indexPath) 99 | cell?.backgroundColor = .clear 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /SwiftARlps/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Application's delegate. 6 | */ 7 | 8 | import UIKit 9 | import ARKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { 16 | BuddyBuildSDK.setup() 17 | 18 | guard ARWorldTrackingConfiguration.isSupported else { 19 | fatalError(""" 20 | ARKit is not available on this device. For apps that require ARKit 21 | for core functionality, use the `arkit` key in the key in the 22 | `UIRequiredDeviceCapabilities` section of the Info.plist to prevent 23 | the app from installing. (If the app can't be installed, this error 24 | can't be triggered in a production scenario.) 25 | In apps where AR is an additive feature, use `isSupported` to 26 | determine whether to show UI for launching AR experiences. 27 | """) // For details, see https://developer.apple.com/documentation/arkit 28 | } 29 | 30 | return true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SwiftARlps/Focus Square/FocusSquare.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | SceneKit node giving the user hints about the status of ARKit world tracking. 6 | */ 7 | 8 | import Foundation 9 | import ARKit 10 | 11 | /** 12 | An `SCNNode` which is used to provide uses with visual cues about the status of ARKit world tracking. 13 | - Tag: FocusSquare 14 | */ 15 | class FocusSquare: SCNNode { 16 | // MARK: - Types 17 | 18 | enum State { 19 | case initializing 20 | case featuresDetected(anchorPosition: float3, camera: ARCamera?) 21 | case planeDetected(anchorPosition: float3, planeAnchor: ARPlaneAnchor, camera: ARCamera?) 22 | } 23 | 24 | // MARK: - Configuration Properties 25 | 26 | // Original size of the focus square in meters. 27 | static let size: Float = 0.17 28 | 29 | // Thickness of the focus square lines in meters. 30 | static let thickness: Float = 0.018 31 | 32 | // Scale factor for the focus square when it is closed, w.r.t. the original size. 33 | static let scaleForClosedSquare: Float = 0.97 34 | 35 | // Side length of the focus square segments when it is open (w.r.t. to a 1x1 square). 36 | static let sideLengthForOpenSegments: CGFloat = 0.2 37 | 38 | // Duration of the open/close animation 39 | static let animationDuration = 0.7 40 | 41 | static let primaryColor = #colorLiteral(red: 1, green: 0.8, blue: 0, alpha: 1) 42 | 43 | // Color of the focus square fill. 44 | static let fillColor = #colorLiteral(red: 1, green: 0.9254901961, blue: 0.4117647059, alpha: 1) 45 | 46 | // MARK: - Properties 47 | 48 | /// The most recent position of the focus square based on the current state. 49 | var lastPosition: float3? { 50 | switch state { 51 | case .initializing: return nil 52 | case .featuresDetected(let anchorPosition, _): return anchorPosition 53 | case .planeDetected(let anchorPosition, _, _): return anchorPosition 54 | } 55 | } 56 | 57 | var state: State = .initializing { 58 | didSet { 59 | guard state != oldValue else { return } 60 | 61 | switch state { 62 | case .initializing: 63 | displayAsBillboard() 64 | 65 | case .featuresDetected(let anchorPosition, let camera): 66 | displayAsOpen(at: anchorPosition, camera: camera) 67 | 68 | case .planeDetected(let anchorPosition, let planeAnchor, let camera): 69 | displayAsClosed(at: anchorPosition, planeAnchor: planeAnchor, camera: camera) 70 | } 71 | } 72 | } 73 | 74 | /// Indicates whether the segments of the focus square are disconnected. 75 | private var isOpen = false 76 | 77 | /// Indicates if the square is currently being animated. 78 | private var isAnimating = false 79 | 80 | /// The focus square's most recent positions. 81 | private var recentFocusSquarePositions: [float3] = [] 82 | 83 | /// Previously visited plane anchors. 84 | private var anchorsOfVisitedPlanes: Set = [] 85 | 86 | /// List of the segments in the focus square. 87 | private var segments: [FocusSquare.Segment] = [] 88 | 89 | /// The primary node that controls the position of other `FocusSquare` nodes. 90 | private let positioningNode = SCNNode() 91 | 92 | // MARK: - Initialization 93 | 94 | override init() { 95 | super.init() 96 | opacity = 0.0 97 | 98 | /* 99 | The focus square consists of eight segments as follows, which can be individually animated. 100 | 101 | s1 s2 102 | _ _ 103 | s3 | | s4 104 | 105 | s5 | | s6 106 | - - 107 | s7 s8 108 | */ 109 | let s1 = Segment(name: "s1", corner: .topLeft, alignment: .horizontal) 110 | let s2 = Segment(name: "s2", corner: .topRight, alignment: .horizontal) 111 | let s3 = Segment(name: "s3", corner: .topLeft, alignment: .vertical) 112 | let s4 = Segment(name: "s4", corner: .topRight, alignment: .vertical) 113 | let s5 = Segment(name: "s5", corner: .bottomLeft, alignment: .vertical) 114 | let s6 = Segment(name: "s6", corner: .bottomRight, alignment: .vertical) 115 | let s7 = Segment(name: "s7", corner: .bottomLeft, alignment: .horizontal) 116 | let s8 = Segment(name: "s8", corner: .bottomRight, alignment: .horizontal) 117 | segments = [s1, s2, s3, s4, s5, s6, s7, s8] 118 | 119 | let sl: Float = 0.5 // segment length 120 | let c: Float = FocusSquare.thickness / 2 // correction to align lines perfectly 121 | s1.simdPosition += float3(-(sl / 2 - c), -(sl - c), 0) 122 | s2.simdPosition += float3(sl / 2 - c, -(sl - c), 0) 123 | s3.simdPosition += float3(-sl, -sl / 2, 0) 124 | s4.simdPosition += float3(sl, -sl / 2, 0) 125 | s5.simdPosition += float3(-sl, sl / 2, 0) 126 | s6.simdPosition += float3(sl, sl / 2, 0) 127 | s7.simdPosition += float3(-(sl / 2 - c), sl - c, 0) 128 | s8.simdPosition += float3(sl / 2 - c, sl - c, 0) 129 | 130 | positioningNode.eulerAngles.x = .pi / 2 // Horizontal 131 | positioningNode.simdScale = float3(FocusSquare.size * FocusSquare.scaleForClosedSquare) 132 | for segment in segments { 133 | positioningNode.addChildNode(segment) 134 | } 135 | positioningNode.addChildNode(fillPlane) 136 | 137 | // Always render focus square on top of other content. 138 | displayNodeHierarchyOnTop(true) 139 | 140 | addChildNode(positioningNode) 141 | 142 | // Start the focus square as a billboard. 143 | displayAsBillboard() 144 | } 145 | 146 | required init?(coder aDecoder: NSCoder) { 147 | fatalError("\(#function) has not been implemented") 148 | } 149 | 150 | // MARK: - Appearance 151 | 152 | /// Hides the focus square. 153 | func hide() { 154 | guard action(forKey: "hide") == nil else { return } 155 | 156 | displayNodeHierarchyOnTop(false) 157 | runAction(.fadeOut(duration: 0.5), forKey: "hide") 158 | } 159 | 160 | /// Unhides the focus square. 161 | func unhide() { 162 | guard action(forKey: "unhide") == nil else { return } 163 | 164 | displayNodeHierarchyOnTop(true) 165 | runAction(.fadeIn(duration: 0.5), forKey: "unhide") 166 | } 167 | 168 | /// Displays the focus square parallel to the camera plane. 169 | private func displayAsBillboard() { 170 | eulerAngles.x = -.pi / 2 171 | simdPosition = float3(0, 0, -0.8) 172 | unhide() 173 | performOpenAnimation() 174 | } 175 | 176 | /// Called when a surface has been detected. 177 | private func displayAsOpen(at position: float3, camera: ARCamera?) { 178 | performOpenAnimation() 179 | recentFocusSquarePositions.append(position) 180 | updateTransform(for: position, camera: camera) 181 | } 182 | 183 | /// Called when a plane has been detected. 184 | private func displayAsClosed(at position: float3, planeAnchor: ARPlaneAnchor, camera: ARCamera?) { 185 | performCloseAnimation(flash: !anchorsOfVisitedPlanes.contains(planeAnchor)) 186 | anchorsOfVisitedPlanes.insert(planeAnchor) 187 | recentFocusSquarePositions.append(position) 188 | updateTransform(for: position, camera: camera) 189 | } 190 | 191 | // MARK: Helper Methods 192 | 193 | /// Update the transform of the focus square to be aligned with the camera. 194 | private func updateTransform(for position: float3, camera: ARCamera?) { 195 | simdTransform = matrix_identity_float4x4 196 | 197 | // Average using several most recent positions. 198 | recentFocusSquarePositions = Array(recentFocusSquarePositions.suffix(10)) 199 | 200 | // Move to average of recent positions to avoid jitter. 201 | let average = recentFocusSquarePositions.reduce(float3(0), { $0 + $1 }) / Float(recentFocusSquarePositions.count) 202 | self.simdPosition = average 203 | self.simdScale = float3(scaleBasedOnDistance(camera: camera)) 204 | 205 | // Correct y rotation of camera square. 206 | guard let camera = camera else { return } 207 | let tilt = abs(camera.eulerAngles.x) 208 | let threshold1: Float = .pi / 2 * 0.65 209 | let threshold2: Float = .pi / 2 * 0.75 210 | let yaw = atan2f(camera.transform.columns.0.x, camera.transform.columns.1.x) 211 | var angle: Float = 0 212 | 213 | switch tilt { 214 | case 0.. Float { 229 | // Normalize angle in steps of 90 degrees such that the rotation to the other angle is minimal 230 | var normalized = angle 231 | while abs(normalized - ref) > .pi / 4 { 232 | if angle > ref { 233 | normalized -= .pi / 2 234 | } else { 235 | normalized += .pi / 2 236 | } 237 | } 238 | return normalized 239 | } 240 | 241 | /** 242 | Reduce visual size change with distance by scaling up when close and down when far away. 243 | 244 | These adjustments result in a scale of 1.0x for a distance of 0.7 m or less 245 | (estimated distance when looking at a table), and a scale of 1.2x 246 | for a distance 1.5 m distance (estimated distance when looking at the floor). 247 | */ 248 | private func scaleBasedOnDistance(camera: ARCamera?) -> Float { 249 | guard let camera = camera else { return 1.0 } 250 | 251 | let distanceFromCamera = simd_length(simdWorldPosition - camera.transform.translation) 252 | if distanceFromCamera < 0.7 { 253 | return distanceFromCamera / 0.7 254 | } else { 255 | return 0.25 * distanceFromCamera + 0.825 256 | } 257 | } 258 | 259 | // MARK: Animations 260 | 261 | private func performOpenAnimation() { 262 | guard !isOpen, !isAnimating else { return } 263 | isOpen = true 264 | isAnimating = true 265 | 266 | // Open animation 267 | SCNTransaction.begin() 268 | SCNTransaction.animationTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) 269 | SCNTransaction.animationDuration = FocusSquare.animationDuration / 4 270 | positioningNode.opacity = 1.0 271 | for segment in segments { 272 | segment.open() 273 | } 274 | SCNTransaction.completionBlock = { 275 | self.positioningNode.runAction(pulseAction(), forKey: "pulse") 276 | // This is a safe operation because `SCNTransaction`'s completion block is called back on the main thread. 277 | self.isAnimating = false 278 | } 279 | SCNTransaction.commit() 280 | 281 | // Add a scale/bounce animation. 282 | SCNTransaction.begin() 283 | SCNTransaction.animationTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) 284 | SCNTransaction.animationDuration = FocusSquare.animationDuration / 4 285 | positioningNode.simdScale = float3(FocusSquare.size) 286 | SCNTransaction.commit() 287 | } 288 | 289 | private func performCloseAnimation(flash: Bool = false) { 290 | guard isOpen, !isAnimating else { return } 291 | isOpen = false 292 | isAnimating = true 293 | 294 | positioningNode.removeAction(forKey: "pulse") 295 | positioningNode.opacity = 1.0 296 | 297 | // Close animation 298 | SCNTransaction.begin() 299 | SCNTransaction.animationTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) 300 | SCNTransaction.animationDuration = FocusSquare.animationDuration / 2 301 | positioningNode.opacity = 0.99 302 | SCNTransaction.completionBlock = { 303 | SCNTransaction.begin() 304 | SCNTransaction.animationTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) 305 | SCNTransaction.animationDuration = FocusSquare.animationDuration / 4 306 | for segment in self.segments { 307 | segment.close() 308 | } 309 | SCNTransaction.completionBlock = { self.isAnimating = false } 310 | SCNTransaction.commit() 311 | } 312 | SCNTransaction.commit() 313 | 314 | // Scale/bounce animation 315 | positioningNode.addAnimation(scaleAnimation(for: "transform.scale.x"), forKey: "transform.scale.x") 316 | positioningNode.addAnimation(scaleAnimation(for: "transform.scale.y"), forKey: "transform.scale.y") 317 | positioningNode.addAnimation(scaleAnimation(for: "transform.scale.z"), forKey: "transform.scale.z") 318 | 319 | if flash { 320 | let waitAction = SCNAction.wait(duration: FocusSquare.animationDuration * 0.75) 321 | let fadeInAction = SCNAction.fadeOpacity(to: 0.25, duration: FocusSquare.animationDuration * 0.125) 322 | let fadeOutAction = SCNAction.fadeOpacity(to: 0.0, duration: FocusSquare.animationDuration * 0.125) 323 | fillPlane.runAction(SCNAction.sequence([waitAction, fadeInAction, fadeOutAction])) 324 | 325 | let flashSquareAction = flashAnimation(duration: FocusSquare.animationDuration * 0.25) 326 | for segment in segments { 327 | segment.runAction(.sequence([waitAction, flashSquareAction])) 328 | } 329 | } 330 | } 331 | 332 | // MARK: Convenience Methods 333 | 334 | private func scaleAnimation(for keyPath: String) -> CAKeyframeAnimation { 335 | let scaleAnimation = CAKeyframeAnimation(keyPath: keyPath) 336 | 337 | let easeOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) 338 | let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 339 | let linear = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) 340 | 341 | let size = FocusSquare.size 342 | let ts = FocusSquare.size * FocusSquare.scaleForClosedSquare 343 | let values = [size, size * 1.15, size * 1.15, ts * 0.97, ts] 344 | let keyTimes: [NSNumber] = [0.00, 0.25, 0.50, 0.75, 1.00] 345 | let timingFunctions = [easeOut, linear, easeOut, easeInOut] 346 | 347 | scaleAnimation.values = values 348 | scaleAnimation.keyTimes = keyTimes 349 | scaleAnimation.timingFunctions = timingFunctions 350 | scaleAnimation.duration = FocusSquare.animationDuration 351 | 352 | return scaleAnimation 353 | } 354 | 355 | /// Sets the rendering order of the `positioningNode` to show on top or under other scene content. 356 | func displayNodeHierarchyOnTop(_ isOnTop: Bool) { 357 | // Recursivley traverses the node's children to update the rendering order depending on the `isOnTop` parameter. 358 | func updateRenderOrder(for node: SCNNode) { 359 | node.renderingOrder = isOnTop ? 2 : 0 360 | 361 | for material in node.geometry?.materials ?? [] { 362 | material.readsFromDepthBuffer = !isOnTop 363 | } 364 | 365 | for child in node.childNodes { 366 | updateRenderOrder(for: child) 367 | } 368 | } 369 | 370 | updateRenderOrder(for: positioningNode) 371 | } 372 | 373 | private lazy var fillPlane: SCNNode = { 374 | let correctionFactor = FocusSquare.thickness / 2 // correction to align lines perfectly 375 | let length = CGFloat(1.0 - FocusSquare.thickness * 2 + correctionFactor) 376 | 377 | let plane = SCNPlane(width: length, height: length) 378 | let node = SCNNode(geometry: plane) 379 | node.name = "fillPlane" 380 | node.opacity = 0.0 381 | 382 | let material = plane.firstMaterial! 383 | material.diffuse.contents = FocusSquare.fillColor 384 | material.isDoubleSided = true 385 | material.ambient.contents = UIColor.black 386 | material.lightingModel = .constant 387 | material.emission.contents = FocusSquare.fillColor 388 | 389 | return node 390 | }() 391 | } 392 | 393 | // MARK: - Animations and Actions 394 | 395 | private func pulseAction() -> SCNAction { 396 | let pulseOutAction = SCNAction.fadeOpacity(to: 0.4, duration: 0.5) 397 | let pulseInAction = SCNAction.fadeOpacity(to: 1.0, duration: 0.5) 398 | pulseOutAction.timingMode = .easeInEaseOut 399 | pulseInAction.timingMode = .easeInEaseOut 400 | 401 | return SCNAction.repeatForever(SCNAction.sequence([pulseOutAction, pulseInAction])) 402 | } 403 | 404 | private func flashAnimation(duration: TimeInterval) -> SCNAction { 405 | let action = SCNAction.customAction(duration: duration) { (node, elapsedTime) -> Void in 406 | // animate color from HSB 48/100/100 to 48/30/100 and back 407 | let elapsedTimePercentage = elapsedTime / CGFloat(duration) 408 | let saturation = 2.8 * (elapsedTimePercentage - 0.5) * (elapsedTimePercentage - 0.5) + 0.3 409 | if let material = node.geometry?.firstMaterial { 410 | material.diffuse.contents = UIColor(hue: 0.1333, saturation: saturation, brightness: 1.0, alpha: 1.0) 411 | } 412 | } 413 | return action 414 | } 415 | 416 | extension FocusSquare.State: Equatable { 417 | static func ==(lhs: FocusSquare.State, rhs: FocusSquare.State) -> Bool { 418 | switch (lhs, rhs) { 419 | case (.initializing, .initializing): 420 | return true 421 | 422 | case (.featuresDetected(let lhsPosition, let lhsCamera), 423 | .featuresDetected(let rhsPosition, let rhsCamera)): 424 | return lhsPosition == rhsPosition && lhsCamera == rhsCamera 425 | 426 | case (.planeDetected(let lhsPosition, let lhsPlaneAnchor, let lhsCamera), 427 | .planeDetected(let rhsPosition, let rhsPlaneAnchor, let rhsCamera)): 428 | return lhsPosition == rhsPosition 429 | && lhsPlaneAnchor == rhsPlaneAnchor 430 | && lhsCamera == rhsCamera 431 | 432 | default: 433 | return false 434 | } 435 | } 436 | } 437 | 438 | -------------------------------------------------------------------------------- /SwiftARlps/Focus Square/FocusSquareSegment.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Corner segments for the focus square UI. 6 | */ 7 | 8 | import SceneKit 9 | 10 | extension FocusSquare { 11 | 12 | /* 13 | The focus square consists of eight segments as follows, which can be individually animated. 14 | 15 | s1 s2 16 | _ _ 17 | s3 | | s4 18 | 19 | s5 | | s6 20 | - - 21 | s7 s8 22 | */ 23 | enum Corner { 24 | case topLeft // s1, s3 25 | case topRight // s2, s4 26 | case bottomRight // s6, s8 27 | case bottomLeft // s5, s7 28 | } 29 | 30 | enum Alignment { 31 | case horizontal // s1, s2, s7, s8 32 | case vertical // s3, s4, s5, s6 33 | } 34 | 35 | enum Direction { 36 | case up, down, left, right 37 | 38 | var reversed: Direction { 39 | switch self { 40 | case .up: return .down 41 | case .down: return .up 42 | case .left: return .right 43 | case .right: return .left 44 | } 45 | } 46 | } 47 | 48 | class Segment: SCNNode { 49 | 50 | // MARK: - Configuration & Initialization 51 | 52 | /// Thickness of the focus square lines in m. 53 | static let thickness: CGFloat = 0.018 54 | 55 | /// Length of the focus square lines in m. 56 | static let length: CGFloat = 0.5 // segment length 57 | 58 | /// Side length of the focus square segments when it is open (w.r.t. to a 1x1 square). 59 | static let openLength: CGFloat = 0.2 60 | 61 | let corner: Corner 62 | let alignment: Alignment 63 | let plane: SCNPlane 64 | 65 | init(name: String, corner: Corner, alignment: Alignment) { 66 | self.corner = corner 67 | self.alignment = alignment 68 | 69 | switch alignment { 70 | case .vertical: 71 | plane = SCNPlane(width: Segment.thickness, height: Segment.length) 72 | case .horizontal: 73 | plane = SCNPlane(width: Segment.length, height: Segment.thickness) 74 | } 75 | super.init() 76 | self.name = name 77 | 78 | let material = plane.firstMaterial! 79 | material.diffuse.contents = FocusSquare.primaryColor 80 | material.isDoubleSided = true 81 | material.ambient.contents = UIColor.black 82 | material.lightingModel = .constant 83 | material.emission.contents = FocusSquare.primaryColor 84 | geometry = plane 85 | } 86 | 87 | required init?(coder aDecoder: NSCoder) { 88 | fatalError("\(#function) has not been implemented") 89 | } 90 | 91 | // MARK: - Animating Open/Closed 92 | 93 | var openDirection: Direction { 94 | switch (corner, alignment) { 95 | case (.topLeft, .horizontal): return .left 96 | case (.topLeft, .vertical): return .up 97 | case (.topRight, .horizontal): return .right 98 | case (.topRight, .vertical): return .up 99 | case (.bottomLeft, .horizontal): return .left 100 | case (.bottomLeft, .vertical): return .down 101 | case (.bottomRight, .horizontal): return .right 102 | case (.bottomRight, .vertical): return .down 103 | } 104 | } 105 | 106 | func open() { 107 | if alignment == .horizontal { 108 | plane.width = Segment.openLength 109 | } else { 110 | plane.height = Segment.openLength 111 | } 112 | 113 | let offset = Segment.length / 2 - Segment.openLength / 2 114 | updatePosition(withOffset: Float(offset), for: openDirection) 115 | } 116 | 117 | func close() { 118 | let oldLength: CGFloat 119 | if alignment == .horizontal { 120 | oldLength = plane.width 121 | plane.width = Segment.length 122 | } else { 123 | oldLength = plane.height 124 | plane.height = Segment.length 125 | } 126 | 127 | let offset = Segment.length / 2 - oldLength / 2 128 | updatePosition(withOffset: Float(offset), for: openDirection.reversed) 129 | } 130 | 131 | private func updatePosition(withOffset offset: Float, for direction: Direction) { 132 | switch direction { 133 | case .left: position.x -= offset 134 | case .right: position.x += offset 135 | case .up: position.y -= offset 136 | case .down: position.y += offset 137 | } 138 | } 139 | 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-1.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-10.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-11.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-12.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-13.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-2.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-3.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-4.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-5.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-6.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-7.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-8.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy-9.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D copy.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/AR_icon_3D.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "AR_icon_3D copy-2.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "AR_icon_3D copy-3.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "AR_icon_3D copy-6.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "AR_icon_3D copy-8.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "AR_icon_3D copy.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "AR_icon_3D copy-5.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "AR_icon_3D copy-4.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "AR_icon_3D copy-9.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "idiom" : "ipad", 53 | "size" : "20x20", 54 | "scale" : "1x" 55 | }, 56 | { 57 | "size" : "20x20", 58 | "idiom" : "ipad", 59 | "filename" : "AR_icon_3D copy-10.png", 60 | "scale" : "2x" 61 | }, 62 | { 63 | "idiom" : "ipad", 64 | "size" : "29x29", 65 | "scale" : "1x" 66 | }, 67 | { 68 | "size" : "29x29", 69 | "idiom" : "ipad", 70 | "filename" : "AR_icon_3D copy-7.png", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "40x40", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "size" : "40x40", 80 | "idiom" : "ipad", 81 | "filename" : "AR_icon_3D copy-1.png", 82 | "scale" : "2x" 83 | }, 84 | { 85 | "size" : "76x76", 86 | "idiom" : "ipad", 87 | "filename" : "AR_icon_3D copy-11.png", 88 | "scale" : "1x" 89 | }, 90 | { 91 | "size" : "76x76", 92 | "idiom" : "ipad", 93 | "filename" : "AR_icon_3D copy-12.png", 94 | "scale" : "2x" 95 | }, 96 | { 97 | "size" : "83.5x83.5", 98 | "idiom" : "ipad", 99 | "filename" : "AR_icon_3D copy-13.png", 100 | "scale" : "2x" 101 | }, 102 | { 103 | "size" : "1024x1024", 104 | "idiom" : "ios-marketing", 105 | "filename" : "AR_icon_3D.png", 106 | "scale" : "1x" 107 | } 108 | ], 109 | "info" : { 110 | "version" : 1, 111 | "author" : "xcode" 112 | } 113 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/add.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "add@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "add@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/add.imageset/add@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/add.imageset/add@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/add.imageset/add@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/add.imageset/add@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/addPressed.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "addPressed@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "addpressed@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/addPressed.imageset/addPressed@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/addPressed.imageset/addPressed@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/addPressed.imageset/addpressed@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/addPressed.imageset/addpressed@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/buttonring.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "ring@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "ring@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/buttonring.imageset/ring@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/buttonring.imageset/ring@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/buttonring.imageset/ring@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/buttonring.imageset/ring@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/candle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "candle@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "candle@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | }, 22 | "properties" : { 23 | "template-rendering-intent" : "template" 24 | } 25 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/candle.imageset/candle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/candle.imageset/candle@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/candle.imageset/candle@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/candle.imageset/candle@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/chair.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "chair@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "chair@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | }, 22 | "properties" : { 23 | "template-rendering-intent" : "template" 24 | } 25 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/chair.imageset/chair@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/chair.imageset/chair@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/chair.imageset/chair@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/chair.imageset/chair@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/cup.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "cup@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "cup@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | }, 22 | "properties" : { 23 | "template-rendering-intent" : "template" 24 | } 25 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/cup.imageset/cup@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/cup.imageset/cup@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/cup.imageset/cup@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/cup.imageset/cup@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/lamp.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "tableLamp@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "tableLamp@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | }, 22 | "properties" : { 23 | "template-rendering-intent" : "template" 24 | } 25 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/lamp.imageset/tableLamp@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/lamp.imageset/tableLamp@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/lamp.imageset/tableLamp@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/lamp.imageset/tableLamp@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/polish.imageset/750polish-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/polish.imageset/750polish-1.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/polish.imageset/750polish-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/polish.imageset/750polish-2.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/polish.imageset/750polish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/polish.imageset/750polish.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/polish.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "750polish.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "750polish-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "750polish-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/restart.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "refresh@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "refresh@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/restart.imageset/refresh@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/restart.imageset/refresh@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/restart.imageset/refresh@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/restart.imageset/refresh@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/restartPressed.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "refreshPressed@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "refreshPressed@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/restartPressed.imageset/refreshPressed@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/restartPressed.imageset/refreshPressed@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/restartPressed.imageset/refreshPressed@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/restartPressed.imageset/refreshPressed@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/shutter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "shutter@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "shutter@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/shutter.imageset/shutter@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/shutter.imageset/shutter@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/shutter.imageset/shutter@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/shutter.imageset/shutter@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/shutterPressed.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "shutterPressed@2px.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "shutterPressed@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/shutterPressed.imageset/shutterPressed@2px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/shutterPressed.imageset/shutterPressed@2px.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/shutterPressed.imageset/shutterPressed@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/shutterPressed.imageset/shutterPressed@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/sweden.imageset/750swedish-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/sweden.imageset/750swedish-1.jpg -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/sweden.imageset/750swedish-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/sweden.imageset/750swedish-2.jpg -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/sweden.imageset/750swedish.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/sweden.imageset/750swedish.jpg -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/sweden.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "750swedish.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "750swedish-1.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "750swedish-2.jpg", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/swizz.imageset/750swiss-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/swizz.imageset/750swiss-1.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/swizz.imageset/750swiss-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/swizz.imageset/750swiss-2.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/swizz.imageset/750swiss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/swizz.imageset/750swiss.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/swizz.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "750swiss.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "750swiss-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "750swiss-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/vase.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "vase@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "vase@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | }, 22 | "properties" : { 23 | "template-rendering-intent" : "template" 24 | } 25 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/vase.imageset/vase@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/vase.imageset/vase@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Assets.xcassets/vase.imageset/vase@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Assets.xcassets/vase.imageset/vase@3x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/BallMaterial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/BallMaterial.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Default-568h@2x.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/alert-circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "alert-circle.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/alert-circle.imageset/alert-circle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/alert-circle.imageset/alert-circle.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/alert-octagon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "alert-octagon.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/alert-octagon.imageset/alert-octagon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/alert-octagon.imageset/alert-octagon.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/alert-triangle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "alert-triangle.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/alert-triangle.imageset/alert-triangle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/alert-triangle.imageset/alert-triangle.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/anchor.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "anchor.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/anchor.imageset/anchor.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/anchor.imageset/anchor.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/aperture.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aperture.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/aperture.imageset/aperture.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/aperture.imageset/aperture.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-down-left.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow-down-left.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-down-left.imageset/arrow-down-left.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/arrow-down-left.imageset/arrow-down-left.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-down-right.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow-down-right.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-down-right.imageset/arrow-down-right.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/arrow-down-right.imageset/arrow-down-right.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-down.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow-down.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-down.imageset/arrow-down.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/arrow-down.imageset/arrow-down.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-left.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow-left.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-left.imageset/arrow-left.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/arrow-left.imageset/arrow-left.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-right.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow-right.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-right.imageset/arrow-right.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/arrow-right.imageset/arrow-right.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-up-left.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow-up-left.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-up-left.imageset/arrow-up-left.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/arrow-up-left.imageset/arrow-up-left.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-up-right.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow-up-right.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-up-right.imageset/arrow-up-right.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/arrow-up-right.imageset/arrow-up-right.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-up.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow-up.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/arrow-up.imageset/arrow-up.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/arrow-up.imageset/arrow-up.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/award.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "award.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/award.imageset/award.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/award.imageset/award.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/bookmark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "bookmark.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/bookmark.imageset/bookmark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/bookmark.imageset/bookmark.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/box.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "box.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/box.imageset/box.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/box.imageset/box.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "circle.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/circle.imageset/circle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/circle.imageset/circle.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/clipboard.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "clipboard.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/clipboard.imageset/clipboard.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/clipboard.imageset/clipboard.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/cloud-snow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cloud-snow.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/cloud-snow.imageset/cloud-snow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/cloud-snow.imageset/cloud-snow.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/codepen.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "codepen.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/codepen.imageset/codepen.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/codepen.imageset/codepen.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/copy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "copy.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/copy.imageset/copy.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/copy.imageset/copy.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/crop.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "crop.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/crop.imageset/crop.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/crop.imageset/crop.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/crosshair.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "crosshair.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/crosshair.imageset/crosshair.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/crosshair.imageset/crosshair.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/droplet.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "droplet.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/droplet.imageset/droplet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/droplet.imageset/droplet.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/grid.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "grid.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/grid.imageset/grid.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/grid.imageset/grid.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/heart.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "heart.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/heart.imageset/heart.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/heart.imageset/heart.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/help-circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "help-circle.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/help-circle.imageset/help-circle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/help-circle.imageset/help-circle.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/layers.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "layers.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/layers.imageset/layers.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/layers.imageset/layers.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/lock.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "lock.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/lock.imageset/lock.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/lock.imageset/lock.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/maximize.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "maximize.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/maximize.imageset/maximize.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/maximize.imageset/maximize.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/minus-circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "minus-circle.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/minus-circle.imageset/minus-circle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/minus-circle.imageset/minus-circle.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/more-horizontal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "more-horizontal.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/more-horizontal.imageset/more-horizontal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/more-horizontal.imageset/more-horizontal.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/more-vertical.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "more-vertical.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/more-vertical.imageset/more-vertical.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/more-vertical.imageset/more-vertical.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/move.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "move.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/move.imageset/move.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/move.imageset/move.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/octagon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "octagon.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/octagon.imageset/octagon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/octagon.imageset/octagon.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/package.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "package.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/package.imageset/package.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/package.imageset/package.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/paperclip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "paperclip.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/paperclip.imageset/paperclip.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/paperclip.imageset/paperclip.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/plus-circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "plus-circle.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/plus-circle.imageset/plus-circle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/plus-circle.imageset/plus-circle.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/rotate-ccw.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "rotate-ccw.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/rotate-ccw.imageset/rotate-ccw.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/rotate-ccw.imageset/rotate-ccw.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/rotate-cw.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "rotate-cw.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/rotate-cw.imageset/rotate-cw.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/rotate-cw.imageset/rotate-cw.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/settings.imageset/settings.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/settings.imageset/settings.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/shield.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "shield.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/shield.imageset/shield.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/shield.imageset/shield.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/shuffle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "shuffle.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/shuffle.imageset/shuffle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/shuffle.imageset/shuffle.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/sliders.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "sliders.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/sliders.imageset/sliders.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/sliders.imageset/sliders.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/star.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "star.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/star.imageset/star.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/star.imageset/star.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/tag.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tag.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/tag.imageset/tag.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/tag.imageset/tag.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/target.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "target.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/target.imageset/target.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/target.imageset/target.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/trash-2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "trash-2.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/trash-2.imageset/trash-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/trash-2.imageset/trash-2.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/triangle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "triangle.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/triangle.imageset/triangle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/triangle.imageset/triangle.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/wind.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wind.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/wind.imageset/wind.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/wind.imageset/wind.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/zap.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "zap.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/zap.imageset/zap.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/zap.imageset/zap.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/zoom-in.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "zoom-in.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/zoom-in.imageset/zoom-in.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/zoom-in.imageset/zoom-in.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/zoom-out.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "zoom-out.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /SwiftARlps/Resources/Icons.xcassets/zoom-out.imageset/zoom-out.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Icons.xcassets/zoom-out.imageset/zoom-out.pdf -------------------------------------------------------------------------------- /SwiftARlps/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | SwiftARlps 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | The camera is used for augmenting reality 27 | NSLocationWhenInUseUsageDescription 28 | The location is used for augmenting reality 29 | NSPhotoLibraryAddUsageDescription 30 | Save photos of your ARExperience 31 | NSPhotoLibraryUsageDescription 32 | Save photos of your AR experience 33 | UILaunchStoryboardName 34 | LaunchScreen 35 | UIMainStoryboardFile 36 | Main 37 | UIRequiredDeviceCapabilities 38 | 39 | armv7 40 | arkit 41 | 42 | UIRequiresFullScreen 43 | 44 | UIStatusBarHidden 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | 54 | UIViewControllerBasedStatusBarAppearance 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /SwiftARlps/Resources/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/candle/candle.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/candle/candle.scn -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/candle/textures/candle_AO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/candle/textures/candle_AO.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/candle/textures/candle_DIFFUSE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/candle/textures/candle_DIFFUSE.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/candle/textures/candle_METALLIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/candle/textures/candle_METALLIC.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/candle/textures/candle_NORMAL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/candle/textures/candle_NORMAL.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/candle/textures/candle_ROUGHNESS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/candle/textures/candle_ROUGHNESS.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/candle/textures/candle_SHADOW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/candle/textures/candle_SHADOW.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/candle/textures/flame_PARTICLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/candle/textures/flame_PARTICLE.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/chair/chair.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/chair/chair.scn -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/chair/textures/chair_DIFFUSE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/chair/textures/chair_DIFFUSE.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/chair/textures/chair_METALLIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/chair/textures/chair_METALLIC.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/chair/textures/chair_NORMAL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/chair/textures/chair_NORMAL.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/chair/textures/chair_ROUGHNESS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/chair/textures/chair_ROUGHNESS.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/chair/textures/chair_SHADOW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/chair/textures/chair_SHADOW.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/cup/cup.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/cup/cup.scn -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/cup/textures/cup_AO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/cup/textures/cup_AO.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/cup/textures/cup_DIFFUSE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/cup/textures/cup_DIFFUSE.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/cup/textures/cup_METALLIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/cup/textures/cup_METALLIC.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/cup/textures/cup_NORMAL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/cup/textures/cup_NORMAL.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/cup/textures/cup_ROUGHNESS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/cup/textures/cup_ROUGHNESS.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/cup/textures/cup_SHADOW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/cup/textures/cup_SHADOW.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/cup/textures/cup_steam_SPRITE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/cup/textures/cup_steam_SPRITE.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/lamp/lamp.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/lamp/lamp.scn -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/lamp/textures/lamp_DIFFUSE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/lamp/textures/lamp_DIFFUSE.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/lamp/textures/lamp_METALLIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/lamp/textures/lamp_METALLIC.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/lamp/textures/lamp_NORMAL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/lamp/textures/lamp_NORMAL.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/lamp/textures/lamp_ROUGHNESS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/lamp/textures/lamp_ROUGHNESS.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/lamp/textures/lamp_SHADOW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/lamp/textures/lamp_SHADOW.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/sharedImages/environment.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/sharedImages/environment.jpg -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/sharedImages/environment_blur.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/sharedImages/environment_blur.exr -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/vase/textures/vase_AO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/vase/textures/vase_AO.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/vase/textures/vase_DIFFUSE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/vase/textures/vase_DIFFUSE.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/vase/textures/vase_METALLIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/vase/textures/vase_METALLIC.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/vase/textures/vase_NORMAL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/vase/textures/vase_NORMAL.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/vase/textures/vase_ROUGHNESS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/vase/textures/vase_ROUGHNESS.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/vase/textures/vase_SHADOW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/vase/textures/vase_SHADOW.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/vase/textures/water_DIFFUSE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/vase/textures/water_DIFFUSE.png -------------------------------------------------------------------------------- /SwiftARlps/Resources/Models.scnassets/vase/vase.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSwiftAlps/SwiftARlps/1de9977022af52244f642711dd70587f50294d73/SwiftARlps/Resources/Models.scnassets/vase/vase.scn -------------------------------------------------------------------------------- /SwiftARlps/SCNNode+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SCNNode+Extensions.swift 3 | // SwiftARlps 4 | // 5 | // Created by Bojan Markovic on 11/24/17. 6 | // Copyright © 2017 Apple. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SceneKit 11 | 12 | 13 | extension SCNNode { 14 | func changeScaleIfPhysicsBodyIncluded(forSize size: CGFloat) { 15 | self.physicsBody = nil 16 | self.scale = SCNVector3(size, size, size) 17 | self.physicsBody = .dynamic() 18 | } 19 | 20 | func changeColor(color: UIColor) { 21 | let material = getMaterial(for: color) 22 | self.geometry?.materials = [material] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SwiftARlps/ThresholdPanGesture.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Contains `ThresholdPanGesture` - a custom `UIPanGestureRecognizer` to track a translation threshold for panning. 6 | */ 7 | 8 | import UIKit.UIGestureRecognizerSubclass 9 | 10 | /** 11 | A custom `UIPanGestureRecognizer` to track when a translation threshold has been exceeded 12 | and panning should begin. 13 | 14 | - Tag: ThresholdPanGesture 15 | */ 16 | class ThresholdPanGesture: UIPanGestureRecognizer { 17 | 18 | /// Indicates whether the currently active gesture has exceeeded the threshold. 19 | private(set) var isThresholdExceeded = false 20 | 21 | /// Observe when the gesture's `state` changes to reset the threshold. 22 | override var state: UIGestureRecognizerState { 23 | didSet { 24 | switch state { 25 | case .began, .changed: 26 | break 27 | 28 | default: 29 | // Reset threshold check. 30 | isThresholdExceeded = false 31 | } 32 | } 33 | } 34 | 35 | /// Returns the threshold value that should be used dependent on the number of touches. 36 | private static func threshold(forTouchCount count: Int) -> CGFloat { 37 | switch count { 38 | case 1: return 30 39 | 40 | // Use a higher threshold for gestures using more than 1 finger. This gives other gestures priority. 41 | default: return 60 42 | } 43 | } 44 | 45 | /// - Tag: touchesMoved 46 | override func touchesMoved(_ touches: Set, with event: UIEvent) { 47 | super.touchesMoved(touches, with: event) 48 | 49 | let translationMagnitude = translation(in: view).length 50 | 51 | // Adjust the threshold based on the number of touches being used. 52 | let threshold = ThresholdPanGesture.threshold(forTouchCount: touches.count) 53 | 54 | if !isThresholdExceeded && translationMagnitude > threshold { 55 | isThresholdExceeded = true 56 | 57 | // Set the overall translation to zero as the gesture should now begin. 58 | setTranslation(.zero, in: view) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SwiftARlps/Utilities/Utilities.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Utility functions and type extensions used throughout the projects. 6 | */ 7 | 8 | import Foundation 9 | import ARKit 10 | 11 | // MARK: - float4x4 extensions 12 | 13 | extension float4x4 { 14 | /** 15 | Treats matrix as a (right-hand column-major convention) transform matrix 16 | and factors out the translation component of the transform. 17 | */ 18 | var translation: float3 { 19 | let translation = columns.3 20 | return float3(translation.x, translation.y, translation.z) 21 | } 22 | } 23 | 24 | // MARK: - CGPoint extensions 25 | 26 | extension CGPoint { 27 | /// Extracts the screen space point from a vector returned by SCNView.projectPoint(_:). 28 | init(_ vector: SCNVector3) { 29 | x = CGFloat(vector.x) 30 | y = CGFloat(vector.y) 31 | } 32 | 33 | /// Returns the length of a point when considered as a vector. (Used with gesture recognizers.) 34 | var length: CGFloat { 35 | return sqrt(x * x + y * y) 36 | } 37 | } 38 | 39 | func getMaterial(for contents: Any) -> SCNMaterial { 40 | let m = SCNMaterial() 41 | m.diffuse.contents = contents 42 | m.lightingModel = .physicallyBased 43 | return m 44 | } 45 | -------------------------------------------------------------------------------- /SwiftARlps/ViewController+ARSCNViewDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | ARSCNViewDelegate interactions for `ViewController`. 6 | */ 7 | 8 | import ARKit 9 | 10 | extension ViewController: ARSCNViewDelegate, ARSessionDelegate { 11 | 12 | // MARK: - ARSCNViewDelegate 13 | 14 | func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { 15 | DispatchQueue.main.async { 16 | self.virtualObjectInteraction.updateObjectToCurrentTrackingPosition() 17 | self.updateFocusSquare() 18 | } 19 | 20 | // If light estimation is enabled, update the intensity of the model's lights and the environment map 21 | let baseIntensity: CGFloat = 40 22 | let lightingEnvironment = sceneView.scene.lightingEnvironment 23 | if let lightEstimate = session.currentFrame?.lightEstimate { 24 | lightingEnvironment.intensity = lightEstimate.ambientIntensity / baseIntensity 25 | } else { 26 | lightingEnvironment.intensity = baseIntensity 27 | } 28 | } 29 | 30 | func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { 31 | guard let planeAnchor = anchor as? ARPlaneAnchor else { return } 32 | DispatchQueue.main.async { 33 | self.statusViewController.cancelScheduledMessage(for: .planeEstimation) 34 | self.statusViewController.showMessage("SURFACE DETECTED") 35 | if self.virtualObjectList.objects.isEmpty { 36 | self.statusViewController.scheduleMessage("TAP A BUTTON TO PLACE AN OBJECT", inSeconds: 7.5, messageType: .contentPlacement) 37 | } 38 | } 39 | let plane = Plane() 40 | planes[planeAnchor] = plane 41 | plane.update(for: planeAnchor) 42 | node.addChildNode(plane) 43 | 44 | updateQueue.async { 45 | for object in self.virtualObjectList.objects { 46 | if object.physicsBody == nil { 47 | object.adjustOntoPlaneAnchor(planeAnchor, using: node) 48 | } 49 | } 50 | } 51 | } 52 | 53 | func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { 54 | guard let planeAnchor = anchor as? ARPlaneAnchor else { return } 55 | 56 | if let plane = planes[planeAnchor] { 57 | plane.update(for: planeAnchor) 58 | } 59 | updateQueue.async { 60 | for object in self.virtualObjectList.objects { 61 | if object.physicsBody == nil { 62 | object.adjustOntoPlaneAnchor(planeAnchor, using: node) 63 | } 64 | } 65 | } 66 | } 67 | 68 | func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) { 69 | statusViewController.showTrackingQualityInfo(for: camera.trackingState, autoHide: true) 70 | 71 | switch camera.trackingState { 72 | case .notAvailable, .limited: 73 | statusViewController.escalateFeedback(for: camera.trackingState, inSeconds: 3.0) 74 | case .normal: 75 | statusViewController.cancelScheduledMessage(for: .trackingStateEscalation) 76 | } 77 | } 78 | 79 | func session(_ session: ARSession, didFailWithError error: Error) { 80 | guard error is ARError else { return } 81 | 82 | let errorWithInfo = error as NSError 83 | let messages = [ 84 | errorWithInfo.localizedDescription, 85 | errorWithInfo.localizedFailureReason, 86 | errorWithInfo.localizedRecoverySuggestion 87 | ] 88 | 89 | // Use `flatMap(_:)` to remove optional error messages. 90 | let errorMessage = messages.flatMap({ $0 }).joined(separator: "\n") 91 | 92 | DispatchQueue.main.async { 93 | self.displayErrorMessage(title: "The AR session failed.", message: errorMessage) 94 | } 95 | } 96 | 97 | func sessionWasInterrupted(_ session: ARSession) { 98 | blurView.isHidden = false 99 | statusViewController.showMessage(""" 100 | SESSION INTERRUPTED 101 | The session will be reset after the interruption has ended. 102 | """, autoHide: false) 103 | } 104 | 105 | func sessionInterruptionEnded(_ session: ARSession) { 106 | blurView.isHidden = true 107 | statusViewController.showMessage("RESETTING SESSION") 108 | 109 | restartExperience() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /SwiftARlps/ViewController+Actions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | UI Actions for the main view controller. 6 | */ 7 | 8 | import UIKit 9 | import SceneKit 10 | 11 | extension ViewController: UIGestureRecognizerDelegate { 12 | 13 | enum SegueIdentifier: String { 14 | case showObjects 15 | } 16 | 17 | // MARK: - Interface Actions 18 | 19 | /// Displays the `ModelObjectSelectionViewController` from the `addObjectButton` or in response to a tap gesture in the `sceneView`. 20 | @IBAction func showModelObjectSelectionViewController() { 21 | // Ensure adding objects is an available action and we are not loading another object (to avoid concurrent modifications of the scene). 22 | guard !addObjectButton.isHidden && !virtualObjectList.isLoading else { return } 23 | 24 | statusViewController.cancelScheduledMessage(for: .contentPlacement) 25 | performSegue(withIdentifier: SegueIdentifier.showObjects.rawValue, sender: addObjectButton) 26 | } 27 | 28 | /// Determines if the tap gesture for presenting the `ModelObjectSelectionViewController` should be used. 29 | func gestureRecognizerShouldBegin(_: UIGestureRecognizer) -> Bool { 30 | return virtualObjectList.objects.isEmpty 31 | } 32 | 33 | @IBAction func addCube() { 34 | guard !addCubeButton.isHidden else { return } 35 | let cube = Cube() 36 | virtualObjectList.add(object: cube) 37 | DispatchQueue.main.async { 38 | self.placeVirtualObject(cube) 39 | } 40 | } 41 | 42 | @IBAction func addBallCube() { 43 | guard !addBallCubeButton.isHidden else { return } 44 | let ballcube = BallCube() 45 | virtualObjectList.add(object: ballcube) 46 | DispatchQueue.main.async { 47 | self.placeVirtualObject(ballcube) 48 | } 49 | } 50 | 51 | @IBAction func addSphere() { 52 | guard !addSphereButton.isHidden else { return } 53 | let sphere = Ball() 54 | virtualObjectList.add(object: sphere) 55 | DispatchQueue.main.async { 56 | self.placeVirtualObject(sphere) 57 | } 58 | } 59 | 60 | @IBAction func didTapRedButton() { 61 | guard !changeColorToRedButton.isHidden else { return } 62 | virtualObjectInteraction.selectedObject?.changeColor(color: UIColor.red) 63 | } 64 | 65 | func gestureRecognizer(_: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith _: UIGestureRecognizer) -> Bool { 66 | return true 67 | } 68 | 69 | /// - Tag: restartExperience 70 | func restartExperience() { 71 | guard isRestartAvailable, !virtualObjectList.isLoading else { return } 72 | isRestartAvailable = false 73 | 74 | statusViewController.cancelAllScheduledMessages() 75 | 76 | virtualObjectList.removeAllObjects() 77 | addObjectButton.setImage(#imageLiteral(resourceName: "candle"), for: []) 78 | addObjectButton.setImage(#imageLiteral(resourceName: "candle"), for: [.highlighted]) 79 | 80 | resetTracking() 81 | 82 | // Disable restart for a while in order to give the session time to restart. 83 | DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { 84 | self.isRestartAvailable = true 85 | } 86 | } 87 | } 88 | 89 | extension ViewController: UIPopoverPresentationControllerDelegate { 90 | 91 | // MARK: - UIPopoverPresentationControllerDelegate 92 | 93 | func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle { 94 | return .none 95 | } 96 | 97 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 98 | // All menus should be popovers (even on iPhone). 99 | if let popoverController = segue.destination.popoverPresentationController, let button = sender as? UIButton { 100 | popoverController.delegate = self 101 | popoverController.sourceView = button 102 | popoverController.sourceRect = button.bounds 103 | } 104 | 105 | guard let identifier = segue.identifier, 106 | let segueIdentifer = SegueIdentifier(rawValue: identifier), 107 | segueIdentifer == .showObjects else { return } 108 | 109 | let objectsViewController = segue.destination as! ModelObjectSelectionViewController 110 | objectsViewController.modelObjects = ModelObject.availableObjects 111 | objectsViewController.delegate = self 112 | 113 | // Set all rows of currently placed objects to selected. 114 | for object in virtualObjectList.loadedModels { 115 | guard let index = ModelObject.availableObjects.index(of: object) else { continue } 116 | objectsViewController.selectedModelObjectRows.insert(index) 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /SwiftARlps/ViewController+ObjectSelection.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Methods on the main view controller for handling virtual object loading and movement 6 | */ 7 | 8 | import UIKit 9 | import SceneKit 10 | 11 | extension ViewController: ModelObjectSelectionViewControllerDelegate { 12 | /** 13 | Adds the specified virtual object to the scene, placed using 14 | the focus square's estimate of the world-space position 15 | currently corresponding to the center of the screen. 16 | 17 | - Tag: PlaceVirtualObject 18 | */ 19 | func placeVirtualObject(_ virtualObject: VirtualObject) { 20 | guard let cameraTransform = session.currentFrame?.camera.transform, 21 | let focusSquarePosition = focusSquare.lastPosition else { 22 | statusViewController.showMessage("CANNOT PLACE OBJECT\nTry moving left or right.") 23 | return 24 | } 25 | 26 | virtualObjectInteraction.selectedObject = virtualObject 27 | virtualObject.setPosition(focusSquarePosition, relativeTo: cameraTransform, smoothMovement: false) 28 | 29 | updateQueue.async { 30 | self.sceneView.scene.rootNode.addChildNode(virtualObject) 31 | } 32 | } 33 | 34 | // MARK: - ModelObjectSelectionViewControllerDelegate 35 | 36 | func modelObjectSelectionViewController(_: ModelObjectSelectionViewController, didSelectObject object: ModelObject) { 37 | virtualObjectList.add(model: object, addedHandler: { [unowned self] loadedObject in 38 | DispatchQueue.main.async { 39 | self.hideObjectLoadingUI() 40 | self.placeVirtualObject(loadedObject) 41 | } 42 | }) 43 | 44 | displayObjectLoadingUI() 45 | } 46 | 47 | func modelObjectSelectionViewController(_: ModelObjectSelectionViewController, didDeselectObject object: ModelObject) { 48 | virtualObjectList.remove(object: object) 49 | } 50 | 51 | // MARK: Object Loading UI 52 | 53 | func displayObjectLoadingUI() { 54 | // Show progress indicator. 55 | spinner.startAnimating() 56 | 57 | addObjectButton.setImage(#imageLiteral(resourceName: "buttonring"), for: []) 58 | 59 | addObjectButton.isEnabled = false 60 | isRestartAvailable = false 61 | } 62 | 63 | func hideObjectLoadingUI() { 64 | // Hide progress indicator. 65 | spinner.stopAnimating() 66 | 67 | addObjectButton.setImage(#imageLiteral(resourceName: "candle"), for: []) 68 | addObjectButton.setImage(#imageLiteral(resourceName: "candle"), for: [.highlighted]) 69 | 70 | addObjectButton.isEnabled = true 71 | isRestartAvailable = true 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SwiftARlps/ViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Main view controller for the AR experience. 6 | */ 7 | 8 | import ARKit 9 | import SceneKit 10 | import UIKit 11 | 12 | class ViewController: UIViewController { 13 | 14 | // MARK: IBOutlets 15 | 16 | @IBOutlet var sceneView: VirtualObjectARView! 17 | 18 | @IBOutlet weak var addObjectButton: UIButton! 19 | 20 | @IBOutlet weak var addCubeButton: UIButton! 21 | @IBOutlet weak var addSphereButton: UIButton! 22 | @IBOutlet weak var addBallCubeButton: UIButton! 23 | @IBOutlet weak var changeColorToRedButton: UIButton! 24 | 25 | @IBOutlet weak var blurView: UIVisualEffectView! 26 | 27 | @IBOutlet weak var spinner: UIActivityIndicatorView! 28 | 29 | // MARK: - UI Elements 30 | 31 | var focusSquare = FocusSquare() 32 | 33 | var planes: [ARPlaneAnchor: Plane] = [:] 34 | 35 | /// The view controller that displays the status and "restart experience" UI. 36 | lazy var statusViewController: StatusViewController = { 37 | return childViewControllers.lazy.flatMap({ $0 as? StatusViewController }).first! 38 | }() 39 | 40 | // MARK: - ARKit Configuration Properties 41 | 42 | /// A type which manages gesture manipulation of virtual content in the scene. 43 | lazy var virtualObjectInteraction = VirtualObjectInteraction(sceneView: sceneView) 44 | 45 | let virtualObjectList = VirtualObjectList() 46 | 47 | /// Marks if the AR experience is available for restart. 48 | var isRestartAvailable = true 49 | 50 | /// A serial queue used to coordinate adding or removing nodes from the scene. 51 | let updateQueue = DispatchQueue(label: "com.example.apple-samplecode.arkitexample.serialSceneKitQueue") 52 | 53 | var screenCenter: CGPoint { 54 | let bounds = sceneView.bounds 55 | return CGPoint(x: bounds.midX, y: bounds.midY) 56 | } 57 | 58 | /// Convenience accessor for the session owned by ARSCNView. 59 | var session: ARSession { 60 | return sceneView.session 61 | } 62 | 63 | // MARK: - View Controller Life Cycle 64 | 65 | override func viewDidLoad() { 66 | super.viewDidLoad() 67 | 68 | sceneView.delegate = self 69 | sceneView.session.delegate = self 70 | sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, SCNDebugOptions.showPhysicsShapes] 71 | 72 | // Set up scene content. 73 | setupCamera() 74 | sceneView.scene.rootNode.addChildNode(focusSquare) 75 | 76 | /* 77 | The `sceneView.automaticallyUpdatesLighting` option creates an 78 | ambient light source and modulates its intensity. This sample app 79 | instead modulates a global lighting environment map for use with 80 | physically based materials, so disable automatic lighting. 81 | */ 82 | sceneView.automaticallyUpdatesLighting = false 83 | if let environmentMap = UIImage(named: "Models.scnassets/sharedImages/environment_blur.exr") { 84 | sceneView.scene.lightingEnvironment.contents = environmentMap 85 | } 86 | 87 | // Hook up status view controller callback(s). 88 | statusViewController.restartExperienceHandler = { [unowned self] in 89 | self.restartExperience() 90 | } 91 | 92 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(showModelObjectSelectionViewController)) 93 | // Set the delegate to ensure this gesture is only used when there are no virtual objects in the scene. 94 | tapGesture.delegate = self 95 | sceneView.addGestureRecognizer(tapGesture) 96 | } 97 | 98 | override func viewDidAppear(_ animated: Bool) { 99 | super.viewDidAppear(animated) 100 | 101 | // Prevent the screen from being dimmed to avoid interuppting the AR experience. 102 | UIApplication.shared.isIdleTimerDisabled = true 103 | 104 | // Start the `ARSession`. 105 | resetTracking() 106 | } 107 | 108 | override func viewWillDisappear(_ animated: Bool) { 109 | super.viewWillDisappear(animated) 110 | 111 | session.pause() 112 | } 113 | 114 | // MARK: - Scene content setup 115 | 116 | func setupCamera() { 117 | guard let camera = sceneView.pointOfView?.camera else { 118 | fatalError("Expected a valid `pointOfView` from the scene.") 119 | } 120 | 121 | /* 122 | Enable HDR camera settings for the most realistic appearance 123 | with environmental lighting and physically based materials. 124 | */ 125 | camera.wantsHDR = true 126 | camera.exposureOffset = -1 127 | camera.minimumExposure = -1 128 | camera.maximumExposure = 3 129 | } 130 | 131 | // MARK: - Session management 132 | 133 | /// Creates a new AR configuration to run on the `session`. 134 | func resetTracking() { 135 | let configuration = ARWorldTrackingConfiguration() 136 | configuration.planeDetection = .horizontal 137 | session.run(configuration, options: [.resetTracking, .removeExistingAnchors]) 138 | 139 | statusViewController.scheduleMessage("FIND A SURFACE TO PLACE AN OBJECT", inSeconds: 7.5, messageType: .planeEstimation) 140 | } 141 | 142 | // MARK: - Focus Square 143 | 144 | func updateFocusSquare() { 145 | let isObjectVisible = virtualObjectList.objects.contains { object in 146 | return sceneView.isNode(object, insideFrustumOf: sceneView.pointOfView!) 147 | } 148 | 149 | if isObjectVisible { 150 | focusSquare.hide() 151 | } else { 152 | focusSquare.unhide() 153 | statusViewController.scheduleMessage("TRY MOVING LEFT OR RIGHT", inSeconds: 5.0, messageType: .focusSquare) 154 | } 155 | 156 | // We should always have a valid world position unless the sceen is just being initialized. 157 | guard let (worldPosition, planeAnchor, _) = sceneView.worldPosition(fromScreenPosition: screenCenter, objectPosition: focusSquare.lastPosition) else { 158 | updateQueue.async { 159 | self.focusSquare.state = .initializing 160 | self.sceneView.pointOfView?.addChildNode(self.focusSquare) 161 | } 162 | addObjectButton.isHidden = true 163 | addCubeButton.isHidden = true 164 | addSphereButton.isHidden = true 165 | addBallCubeButton.isHidden = true 166 | changeColorToRedButton.isHidden = true 167 | return 168 | } 169 | 170 | updateQueue.async { 171 | self.sceneView.scene.rootNode.addChildNode(self.focusSquare) 172 | let camera = self.session.currentFrame?.camera 173 | 174 | if let planeAnchor = planeAnchor { 175 | self.focusSquare.state = .planeDetected(anchorPosition: worldPosition, planeAnchor: planeAnchor, camera: camera) 176 | } else { 177 | self.focusSquare.state = .featuresDetected(anchorPosition: worldPosition, camera: camera) 178 | } 179 | } 180 | addObjectButton.isHidden = false 181 | addCubeButton.isHidden = false 182 | addSphereButton.isHidden = false 183 | addBallCubeButton.isHidden = false 184 | changeColorToRedButton.isHidden = false 185 | statusViewController.cancelScheduledMessage(for: .focusSquare) 186 | } 187 | 188 | // MARK: - Error handling 189 | 190 | func displayErrorMessage(title: String, message: String) { 191 | // Blur the background. 192 | blurView.isHidden = false 193 | 194 | // Present an alert informing about the error that has occurred. 195 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 196 | let restartAction = UIAlertAction(title: "Restart Session", style: .default) { _ in 197 | alertController.dismiss(animated: true, completion: nil) 198 | self.blurView.isHidden = true 199 | self.resetTracking() 200 | } 201 | alertController.addAction(restartAction) 202 | present(alertController, animated: true, completion: nil) 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /SwiftARlps/Virtual Objects/Ball.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sphere.swift 3 | // SwiftARlps 4 | // 5 | // Created by Niels van Hoorn on 24/11/2017. 6 | // Copyright © 2017 Apple. All rights reserved. 7 | // 8 | 9 | import SceneKit 10 | 11 | class Ball: SCNNode, Positionable { 12 | var recentModelObjectDistances: [Float] = [] 13 | 14 | let sphere: SCNSphere 15 | let radius: Float = 0.1 16 | override init() { 17 | self.sphere = SCNSphere(radius: CGFloat(radius)) 18 | let material = getMaterial(for: UIImage(named: "BallMaterial")!) 19 | material.shininess = 1.0 20 | sphere.materials = [material] 21 | super.init() 22 | self.geometry = sphere 23 | let shape = SCNPhysicsShape(geometry: sphere, options: nil) 24 | self.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape) 25 | } 26 | 27 | required init?(coder aDecoder: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /SwiftARlps/Virtual Objects/BallCube.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BallCube.swift 3 | // SwiftARlps 4 | // 5 | // Created by Adrian Kosmaczewski on 24.11.17. 6 | // Copyright © 2017 Apple. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SceneKit 11 | 12 | class BallCube: SCNNode, Positionable { 13 | var recentModelObjectDistances: [Float] = [] 14 | 15 | let box: SCNBox 16 | let ball = Ball() 17 | let cube = Cube() 18 | let size: CGFloat = 0.1 19 | let radius: Float = 0.1 20 | override init() { 21 | ball.physicsBody = nil 22 | cube.physicsBody = nil 23 | self.box = SCNBox(width: size * 2, height: size * 2, length: size * 2, chamferRadius: 0) 24 | let m = SCNMaterial() 25 | m.transparency = 0.0 26 | box.materials = [m] 27 | 28 | super.init() 29 | self.addChildNode(cube) 30 | self.addChildNode(ball) 31 | cube.position = SCNVector3(self.position.x, self.position.y, self.position.z) 32 | ball.position = SCNVector3(0.0, cube.position.x + Float(cube.size), 0.0) 33 | self.geometry = box 34 | let shape = SCNPhysicsShape(geometry: box, options: nil) 35 | self.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape) 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /SwiftARlps/Virtual Objects/Cube.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cube.swift 3 | // ARKitInteraction 4 | // 5 | // Created by Niels van Hoorn on 18/11/2017. 6 | // Copyright © 2017 Apple. All rights reserved. 7 | // 8 | 9 | import SceneKit 10 | 11 | class Cube: SCNNode, Positionable { 12 | var recentModelObjectDistances: [Float] = [] 13 | 14 | var box: SCNBox 15 | var size: CGFloat = 0.1 16 | 17 | override init() { 18 | let image = #imageLiteral(resourceName: "swizz") 19 | size = image.size.width/1000 20 | let box = SCNBox(width: size, 21 | height: size, 22 | length: size, chamferRadius: 0.002) 23 | self.box = box 24 | 25 | let images = [#imageLiteral(resourceName: "swizz"),#imageLiteral(resourceName: "swizz"),#imageLiteral(resourceName: "polish"),#imageLiteral(resourceName: "polish"),#imageLiteral(resourceName: "sweden"),#imageLiteral(resourceName: "sweden")] 26 | let imageMaterials = images.map({ (image) -> SCNMaterial in 27 | let imageMaterial = SCNMaterial() 28 | imageMaterial.diffuse.contents = image 29 | return imageMaterial 30 | }) 31 | 32 | box.materials = imageMaterials 33 | 34 | super.init() 35 | 36 | self.geometry = box 37 | let shape = SCNPhysicsShape(geometry: box, options: nil) 38 | self.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape) 39 | } 40 | 41 | func saveScale(factor: CGFloat) { 42 | size *= factor 43 | } 44 | 45 | func scale(factor: CGFloat) -> Void { 46 | self.box = SCNBox(width: size*factor, 47 | height: size*factor, 48 | length: size*factor, 49 | chamferRadius: 0) 50 | 51 | 52 | let images = [#imageLiteral(resourceName: "swizz"),#imageLiteral(resourceName: "swizz"),#imageLiteral(resourceName: "polish"),#imageLiteral(resourceName: "polish"),#imageLiteral(resourceName: "sweden"),#imageLiteral(resourceName: "sweden")] 53 | let imageMaterials = images.map({ (image) -> SCNMaterial in 54 | let imageMaterial = SCNMaterial() 55 | imageMaterial.diffuse.contents = image 56 | return imageMaterial 57 | }) 58 | 59 | box.materials = imageMaterials 60 | geometry = box 61 | updatePhysicsBody() 62 | } 63 | required init?(coder aDecoder: NSCoder) { 64 | fatalError("init(coder:) has not been implemented") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SwiftARlps/Virtual Objects/ModelObject.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A `SCNReferenceNode` subclass for virtual objects placed into the AR scene. 6 | */ 7 | 8 | import Foundation 9 | import SceneKit 10 | import ARKit 11 | 12 | 13 | class ModelObject: SCNReferenceNode, Positionable { 14 | internal var recentModelObjectDistances: [Float] = [] 15 | /// The model name derived from the `referenceURL`. 16 | var modelName: String { 17 | return referenceURL.lastPathComponent.replacingOccurrences(of: ".scn", with: "") 18 | } 19 | } 20 | 21 | extension ModelObject { 22 | // MARK: Static Properties and Methods 23 | 24 | /// Loads all the model objects within `Models.scnassets`. 25 | static let availableObjects: [ModelObject] = { 26 | let modelsURL = Bundle.main.url(forResource: "Models.scnassets", withExtension: nil)! 27 | 28 | let fileEnumerator = FileManager().enumerator(at: modelsURL, includingPropertiesForKeys: [])! 29 | 30 | return fileEnumerator.flatMap { element in 31 | let url = element as! URL 32 | 33 | guard url.pathExtension == "scn" else { return nil } 34 | 35 | return ModelObject(url: url) 36 | } 37 | }() 38 | 39 | /// Returns a `ModelObject` if one exists as an ancestor to the provided node. 40 | static func existingObjectContainingNode(_ node: SCNNode) -> ModelObject? { 41 | if let modelObjectRoot = node as? ModelObject { 42 | return modelObjectRoot 43 | } 44 | 45 | guard let parent = node.parent else { return nil } 46 | 47 | // Recurse up to check if the parent is a `ModelObject`. 48 | return existingObjectContainingNode(parent) 49 | } 50 | } 51 | 52 | extension Collection where Iterator.Element == Float, IndexDistance == Int { 53 | /// Return the mean of a list of Floats. Used with `recentModelObjectDistances`. 54 | var average: Float? { 55 | guard !isEmpty else { 56 | return nil 57 | } 58 | 59 | let sum = reduce(Float(0)) { current, next -> Float in 60 | return current + next 61 | } 62 | 63 | return sum / Float(count) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /SwiftARlps/Virtual Objects/Plane.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Plane.swift 3 | // SwiftARlps 4 | // 5 | // Created by Niels van Hoorn on 24/11/2017. 6 | // Copyright © 2017 Apple. All rights reserved. 7 | // 8 | 9 | import SceneKit 10 | import ARKit 11 | 12 | let planeHeight: Float = 0.1 13 | 14 | extension SCNNode { 15 | func updatePhysicsBody() { 16 | guard let geometry = geometry, 17 | let currentBody = physicsBody, 18 | let currentShape = currentBody.physicsShape else { 19 | return 20 | } 21 | let shape = SCNPhysicsShape(geometry: geometry, options: currentShape.options) 22 | let body = SCNPhysicsBody(type: currentBody.type, shape: shape) 23 | body.isAffectedByGravity = currentBody.isAffectedByGravity 24 | physicsBody = body 25 | } 26 | } 27 | 28 | class Plane: SCNNode, Positionable { 29 | var recentModelObjectDistances: [Float] = [] 30 | let plane: SCNBox 31 | let planeNode: SCNNode 32 | let planeHeight: Float = 0.1 33 | override init() { 34 | plane = SCNBox(width: 0.5, height: CGFloat(planeHeight), length: 0.5, chamferRadius: 0) 35 | planeNode = SCNNode(geometry: plane) 36 | super.init() 37 | show(false) 38 | addChildNode(planeNode) 39 | } 40 | 41 | func show(_ show: Bool) { 42 | var materials = Array(repeating: getMaterial(for: UIColor.clear), count: 6) 43 | if show { 44 | materials[4] = getMaterial(for: UIColor(white: 1, alpha: 0.3)) 45 | } 46 | plane.materials = materials 47 | } 48 | 49 | func update(for anchor: ARPlaneAnchor) { 50 | plane.width = CGFloat(anchor.extent.x) 51 | plane.length = CGFloat(anchor.extent.z) 52 | planeNode.position = SCNVector3(anchor.center.x, -planeHeight/2, anchor.center.z) 53 | let shape = SCNPhysicsShape(geometry: plane, options: nil) 54 | planeNode.physicsBody = SCNPhysicsBody(type: .static, shape: shape) 55 | planeNode.physicsBody?.isAffectedByGravity = false 56 | } 57 | 58 | required init?(coder aDecoder: NSCoder) { 59 | fatalError("init(coder:) has not been implemented") 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /SwiftARlps/Virtual Objects/Positionable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Positionable.swift 3 | // ARKitInteraction 4 | // 5 | // Created by Niels van Hoorn on 18/11/2017. 6 | // Copyright © 2017 Apple. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SceneKit 11 | import ARKit 12 | 13 | protocol Positionable: class { 14 | var position: SCNVector3 { get set } 15 | var simdPosition: simd_float3 { get set } 16 | var parent: SCNNode? { get } 17 | 18 | /// Use average of recent virtual object distances to avoid rapid changes in object scale. 19 | var recentModelObjectDistances: [Float] { get set } 20 | } 21 | 22 | extension Positionable { 23 | /// - Tag: AdjustOntoPlaneAnchor 24 | func adjustOntoPlaneAnchor(_ anchor: ARPlaneAnchor, using node: SCNNode) { 25 | // Get the object's position in the plane's coordinate system. 26 | let planePosition = node.convertPosition(position, from: parent) 27 | 28 | // Check that the object is not already on the plane. 29 | guard planePosition.y != 0 else { return } 30 | 31 | // Add 10% tolerance to the corners of the plane. 32 | let tolerance: Float = 0.1 33 | 34 | let minX: Float = anchor.center.x - anchor.extent.x / 2 - anchor.extent.x * tolerance 35 | let maxX: Float = anchor.center.x + anchor.extent.x / 2 + anchor.extent.x * tolerance 36 | let minZ: Float = anchor.center.z - anchor.extent.z / 2 - anchor.extent.z * tolerance 37 | let maxZ: Float = anchor.center.z + anchor.extent.z / 2 + anchor.extent.z * tolerance 38 | 39 | guard (minX...maxX).contains(planePosition.x) && (minZ...maxZ).contains(planePosition.z) else { 40 | return 41 | } 42 | 43 | // Move onto the plane if it is near it (within 5 centimeters). 44 | let verticalAllowance: Float = 0.05 45 | let epsilon: Float = 0.001 // Do not update if the difference is less than 1 mm. 46 | let distanceToPlane = abs(planePosition.y) 47 | if distanceToPlane > epsilon && distanceToPlane < verticalAllowance { 48 | SCNTransaction.begin() 49 | SCNTransaction.animationDuration = CFTimeInterval(distanceToPlane * 500) // Move 2 mm per second. 50 | SCNTransaction.animationTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 51 | position.y = anchor.transform.columns.3.y 52 | SCNTransaction.commit() 53 | } 54 | } 55 | 56 | /** 57 | Set the object's position based on the provided position relative to the `cameraTransform`. 58 | If `smoothMovement` is true, the new position will be averaged with previous position to 59 | avoid large jumps. 60 | 61 | - Tag: ModelObjectSetPosition 62 | */ 63 | func setPosition(_ newPosition: float3, relativeTo cameraTransform: matrix_float4x4, smoothMovement: Bool) { 64 | let cameraWorldPosition = cameraTransform.translation 65 | var positionOffsetFromCamera = newPosition - cameraWorldPosition 66 | 67 | // Limit the distance of the object from the camera to a maximum of 10 meters. 68 | if simd_length(positionOffsetFromCamera) > 10 { 69 | positionOffsetFromCamera = simd_normalize(positionOffsetFromCamera) 70 | positionOffsetFromCamera *= 10 71 | } 72 | 73 | /* 74 | Compute the average distance of the object from the camera over the last ten 75 | updates. Notice that the distance is applied to the vector from 76 | the camera to the content, so it affects only the percieved distance to the 77 | object. Averaging does _not_ make the content "lag". 78 | */ 79 | if smoothMovement { 80 | let hitTestResultDistance = simd_length(positionOffsetFromCamera) 81 | 82 | // Add the latest position and keep up to 10 recent distances to smooth with. 83 | recentModelObjectDistances.append(hitTestResultDistance) 84 | recentModelObjectDistances = Array(recentModelObjectDistances.suffix(10)) 85 | 86 | let averageDistance = recentModelObjectDistances.average! 87 | let averagedDistancePosition = simd_normalize(positionOffsetFromCamera) * averageDistance 88 | simdPosition = cameraWorldPosition + averagedDistancePosition 89 | } else { 90 | simdPosition = cameraWorldPosition + positionOffsetFromCamera 91 | } 92 | } 93 | 94 | /// Resets the objects poisition smoothing. 95 | func reset() { 96 | recentModelObjectDistances.removeAll() 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /SwiftARlps/Virtual Objects/VirtualObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VirtualObject.swift 3 | // ARKitInteraction 4 | // 5 | // Created by Niels van Hoorn on 18/11/2017. 6 | // Copyright © 2017 Apple. All rights reserved. 7 | // 8 | 9 | import SceneKit 10 | 11 | typealias VirtualObject = SCNNode & Positionable 12 | -------------------------------------------------------------------------------- /SwiftARlps/Virtual Objects/VirtualObjectList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VirtualObjectManager.swift 3 | // ARKitInteraction 4 | // 5 | // Created by Niels van Hoorn on 18/11/2017. 6 | // Copyright © 2017 Apple. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SceneKit 11 | 12 | class VirtualObjectList { 13 | var loadedModels: [ModelObject] { 14 | return objects.flatMap({ $0 as? ModelObject }) 15 | } 16 | 17 | private(set) var objects: [VirtualObject] = [] 18 | 19 | private(set) var isLoading = false 20 | 21 | // MARK: - Loading object 22 | 23 | /** 24 | Loads a `ModelObject` on a background queue. `loadedHandler` is invoked 25 | on a background queue once `object` has been loaded. 26 | */ 27 | private func loadModelObject(_ object: ModelObject, loadedHandler: @escaping (ModelObject) -> Void) { 28 | isLoading = true 29 | // Load the content asynchronously. 30 | DispatchQueue.global(qos: .userInitiated).async { 31 | object.reset() 32 | object.load() 33 | 34 | self.isLoading = false 35 | loadedHandler(object) 36 | } 37 | } 38 | 39 | func add(object: VirtualObject) { 40 | objects.append(object) 41 | } 42 | 43 | func add(model: ModelObject, addedHandler: @escaping (ModelObject) -> Void) { 44 | loadModelObject(model, loadedHandler: { [unowned self] model in 45 | self.add(object: model) 46 | addedHandler(model) 47 | }) 48 | } 49 | 50 | func remove(object: VirtualObject) { 51 | // This objects.index(of:) would be more logical here, but that doesn't work. 52 | guard let objectIndex = objects.index(where:{ obj in object == obj}) else { 53 | fatalError("Programmer error: Failed to lookup virtual object in scene.") 54 | } 55 | objects[objectIndex].removeFromParentNode() 56 | objects.remove(at: objectIndex) 57 | } 58 | 59 | func removeAllObjects() { 60 | // Reverse the indicies so we don't trample over indicies as objects are removed. 61 | for object in objects.reversed() { 62 | remove(object: object) 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /SwiftARlps/VirtualObjectARView.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | A custom `ARSCNView` configured for the requirements of this project. 6 | */ 7 | 8 | import Foundation 9 | import ARKit 10 | 11 | class VirtualObjectARView: ARSCNView { 12 | 13 | // MARK: - Types 14 | 15 | struct HitTestRay { 16 | var origin: float3 17 | var direction: float3 18 | 19 | func intersectionWithHorizontalPlane(atY planeY: Float) -> float3? { 20 | let normalizedDirection = simd_normalize(direction) 21 | 22 | // Special case handling: Check if the ray is horizontal as well. 23 | if normalizedDirection.y == 0 { 24 | if origin.y == planeY { 25 | /* 26 | The ray is horizontal and on the plane, thus all points on the ray 27 | intersect with the plane. Therefore we simply return the ray origin. 28 | */ 29 | return origin 30 | } else { 31 | // The ray is parallel to the plane and never intersects. 32 | return nil 33 | } 34 | } 35 | 36 | /* 37 | The distance from the ray's origin to the intersection point on the plane is: 38 | (`pointOnPlane` - `rayOrigin`) dot `planeNormal` 39 | -------------------------------------------- 40 | direction dot planeNormal 41 | */ 42 | 43 | // Since we know that horizontal planes have normal (0, 1, 0), we can simplify this to: 44 | let distance = (planeY - origin.y) / normalizedDirection.y 45 | 46 | // Do not return intersections behind the ray's origin. 47 | if distance < 0 { 48 | return nil 49 | } 50 | 51 | // Return the intersection point. 52 | return origin + (normalizedDirection * distance) 53 | } 54 | 55 | } 56 | 57 | struct FeatureHitTestResult { 58 | var position: float3 59 | var distanceToRayOrigin: Float 60 | var featureHit: float3 61 | var featureDistanceToHitResult: Float 62 | } 63 | 64 | // MARK: Position Testing 65 | 66 | /// Hit tests against the `sceneView` to find an object at the provided point. 67 | func virtualObject(at point: CGPoint) -> VirtualObject? { 68 | let hitTestOptions: [SCNHitTestOption: Any] = [.boundingBoxOnly: true] 69 | let hitTestResults = hitTest(point, options: hitTestOptions) 70 | 71 | return hitTestResults.lazy.flatMap { result in 72 | if let node = ModelObject.existingObjectContainingNode(result.node) { 73 | return node 74 | } else { 75 | return result.node as? VirtualObject 76 | } 77 | }.first 78 | } 79 | 80 | /** 81 | Hit tests from the provided screen position to return the most accuarte result possible. 82 | Returns the new world position, an anchor if one was hit, and if the hit test is considered to be on a plane. 83 | */ 84 | func worldPosition(fromScreenPosition position: CGPoint, objectPosition: float3?, infinitePlane: Bool = false) -> (position: float3, planeAnchor: ARPlaneAnchor?, isOnPlane: Bool)? { 85 | /* 86 | 1. Always do a hit test against exisiting plane anchors first. (If any 87 | such anchors exist & only within their extents.) 88 | */ 89 | let planeHitTestResults = hitTest(position, types: .existingPlaneUsingExtent) 90 | 91 | if let result = planeHitTestResults.first { 92 | let planeHitTestPosition = result.worldTransform.translation 93 | let planeAnchor = result.anchor 94 | 95 | // Return immediately - this is the best possible outcome. 96 | return (planeHitTestPosition, planeAnchor as? ARPlaneAnchor, true) 97 | } 98 | 99 | /* 100 | 2. Collect more information about the environment by hit testing against 101 | the feature point cloud, but do not return the result yet. 102 | */ 103 | let featureHitTestResult = hitTestWithFeatures(position, coneOpeningAngleInDegrees: 18, minDistance: 0.2, maxDistance: 2.0).first 104 | let featurePosition = featureHitTestResult?.position 105 | 106 | /* 107 | 3. If desired or necessary (no good feature hit test result): Hit test 108 | against an infinite, horizontal plane (ignoring the real world). 109 | */ 110 | if infinitePlane || featurePosition == nil { 111 | if let objectPosition = objectPosition, 112 | let pointOnInfinitePlane = hitTestWithInfiniteHorizontalPlane(position, objectPosition) { 113 | return (pointOnInfinitePlane, nil, true) 114 | } 115 | } 116 | 117 | /* 118 | 4. If available, return the result of the hit test against high quality 119 | features if the hit tests against infinite planes were skipped or no 120 | infinite plane was hit. 121 | */ 122 | if let featurePosition = featurePosition { 123 | return (featurePosition, nil, false) 124 | } 125 | 126 | /* 127 | 5. As a last resort, perform a second, unfiltered hit test against features. 128 | If there are no features in the scene, the result returned here will be nil. 129 | */ 130 | let unfilteredFeatureHitTestResults = hitTestWithFeatures(position) 131 | if let result = unfilteredFeatureHitTestResults.first { 132 | return (result.position, nil, false) 133 | } 134 | 135 | return nil 136 | } 137 | 138 | // MARK: - Hit Tests 139 | 140 | func hitTestRayFromScreenPosition(_ point: CGPoint) -> HitTestRay? { 141 | guard let frame = session.currentFrame else { return nil } 142 | 143 | let cameraPos = frame.camera.transform.translation 144 | 145 | // Note: z: 1.0 will unproject() the screen position to the far clipping plane. 146 | let positionVec = float3(x: Float(point.x), y: Float(point.y), z: 1.0) 147 | let screenPosOnFarClippingPlane = unprojectPoint(positionVec) 148 | 149 | let rayDirection = simd_normalize(screenPosOnFarClippingPlane - cameraPos) 150 | return HitTestRay(origin: cameraPos, direction: rayDirection) 151 | } 152 | 153 | func hitTestWithInfiniteHorizontalPlane(_ point: CGPoint, _ pointOnPlane: float3) -> float3? { 154 | guard let ray = hitTestRayFromScreenPosition(point) else { return nil } 155 | 156 | // Do not intersect with planes above the camera or if the ray is almost parallel to the plane. 157 | if ray.direction.y > -0.03 { 158 | return nil 159 | } 160 | 161 | /* 162 | Return the intersection of a ray from the camera through the screen position 163 | with a horizontal plane at height (Y axis). 164 | */ 165 | return ray.intersectionWithHorizontalPlane(atY: pointOnPlane.y) 166 | } 167 | 168 | func hitTestWithFeatures(_ point: CGPoint, coneOpeningAngleInDegrees: Float, minDistance: Float = 0, maxDistance: Float = Float.greatestFiniteMagnitude, maxResults: Int = 1) -> [FeatureHitTestResult] { 169 | 170 | guard let features = session.currentFrame?.rawFeaturePoints, let ray = hitTestRayFromScreenPosition(point) else { 171 | return [] 172 | } 173 | 174 | let maxAngleInDegrees = min(coneOpeningAngleInDegrees, 360) / 2 175 | let maxAngle = (maxAngleInDegrees / 180) * .pi 176 | 177 | let results = features.points.flatMap { featurePosition -> FeatureHitTestResult? in 178 | let originToFeature = featurePosition - ray.origin 179 | 180 | let crossProduct = simd_cross(originToFeature, ray.direction) 181 | let featureDistanceFromResult = simd_length(crossProduct) 182 | 183 | let hitTestResult = ray.origin + (ray.direction * simd_dot(ray.direction, originToFeature)) 184 | let hitTestResultDistance = simd_length(hitTestResult - ray.origin) 185 | 186 | if hitTestResultDistance < minDistance || hitTestResultDistance > maxDistance { 187 | // Skip this feature - it is too close or too far away. 188 | return nil 189 | } 190 | 191 | let originToFeatureNormalized = simd_normalize(originToFeature) 192 | let angleBetweenRayAndFeature = acos(simd_dot(ray.direction, originToFeatureNormalized)) 193 | 194 | if angleBetweenRayAndFeature > maxAngle { 195 | // Skip this feature - is is outside of the hit test cone. 196 | return nil 197 | } 198 | 199 | // All tests passed: Add the hit against this feature to the results. 200 | return FeatureHitTestResult(position: hitTestResult, 201 | distanceToRayOrigin: hitTestResultDistance, 202 | featureHit: featurePosition, 203 | featureDistanceToHitResult: featureDistanceFromResult) 204 | } 205 | 206 | // Sort the results by feature distance to the ray origin. 207 | let sortedResults = results.sorted { $0.distanceToRayOrigin < $1.distanceToRayOrigin } 208 | 209 | let remainingResults = maxResults > 0 ? Array(sortedResults.prefix(maxResults)) : sortedResults 210 | 211 | return Array(remainingResults) 212 | } 213 | 214 | func hitTestWithFeatures(_ point: CGPoint) -> [FeatureHitTestResult] { 215 | guard let features = session.currentFrame?.rawFeaturePoints, 216 | let ray = hitTestRayFromScreenPosition(point) else { 217 | return [] 218 | } 219 | 220 | /* 221 | Find the feature point closest to the hit test ray, then create 222 | a hit test result by finding the point on the ray closest to that feature. 223 | */ 224 | let possibleResults = features.points.map { featurePosition in 225 | return FeatureHitTestResult(featurePoint: featurePosition, ray: ray) 226 | } 227 | let closestResult = possibleResults.min(by: { $0.featureDistanceToHitResult < $1.featureDistanceToHitResult })! 228 | return [closestResult] 229 | } 230 | 231 | } 232 | 233 | extension SCNView { 234 | /** 235 | Type conversion wrapper for original `unprojectPoint(_:)` method. 236 | Used in contexts where sticking to SIMD float3 type is helpful. 237 | */ 238 | func unprojectPoint(_ point: float3) -> float3 { 239 | return float3(unprojectPoint(SCNVector3(point))) 240 | } 241 | } 242 | 243 | extension VirtualObjectARView.FeatureHitTestResult { 244 | /// Add a convenience initializer to `FeatureHitTestResult` for `HitTestRay`. 245 | /// By adding the initializer in an extension, we also get the default initializer for `FeatureHitTestResult`. 246 | init(featurePoint: float3, ray: VirtualObjectARView.HitTestRay) { 247 | self.featureHit = featurePoint 248 | 249 | let originToFeature = featurePoint - ray.origin 250 | self.position = ray.origin + (ray.direction * simd_dot(ray.direction, originToFeature)) 251 | self.distanceToRayOrigin = simd_length(self.position - ray.origin) 252 | self.featureDistanceToHitResult = simd_length(simd_cross(originToFeature, ray.direction)) 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /SwiftARlps/VirtualObjectInteraction.swift: -------------------------------------------------------------------------------- 1 | /* 2 | See LICENSE folder for this sample’s licensing information. 3 | 4 | Abstract: 5 | Coordinates movement and gesture interactions with virtual objects. 6 | */ 7 | 8 | import UIKit 9 | import ARKit 10 | 11 | /// - Tag: VirtualObjectInteraction 12 | class VirtualObjectInteraction: NSObject, UIGestureRecognizerDelegate { 13 | 14 | /// Developer setting to translate assuming the detected plane extends infinitely. 15 | let translateAssumingInfinitePlane = true 16 | 17 | /// The scene view to hit test against when moving virtual content. 18 | let sceneView: VirtualObjectARView 19 | 20 | /** 21 | The object that has been most recently intereacted with. 22 | The `selectedObject` can be moved at any time with the tap gesture. 23 | */ 24 | var selectedObject: VirtualObject? 25 | 26 | /// The object that is tracked for use by the pan and rotation gestures. 27 | private var trackedObject: VirtualObject? { 28 | didSet { 29 | guard trackedObject != nil else { return } 30 | selectedObject = trackedObject 31 | } 32 | } 33 | 34 | /// The tracked screen position used to update the `trackedObject`'s position in `updateObjectToCurrentTrackingPosition()`. 35 | private var currentTrackingPositionVector: SCNVector3? 36 | private var currentTrackingPositionPoint: CGPoint? 37 | 38 | init(sceneView: VirtualObjectARView) { 39 | self.sceneView = sceneView 40 | super.init() 41 | 42 | let panGesture = ThresholdPanGesture(target: self, action: #selector(didPan(_:))) 43 | panGesture.delegate = self 44 | 45 | let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(didRotate(_:))) 46 | rotationGesture.delegate = self 47 | 48 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap(_:))) 49 | let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(didPinch(_:))) 50 | 51 | let swipeGestures = [UISwipeGestureRecognizerDirection.down, UISwipeGestureRecognizerDirection.up, UISwipeGestureRecognizerDirection.left, UISwipeGestureRecognizerDirection.right].map { addSwipeGesture(for: $0) } 52 | 53 | // Add gestures to the `sceneView`. 54 | sceneView.addGestureRecognizer(panGesture) 55 | sceneView.addGestureRecognizer(rotationGesture) 56 | sceneView.addGestureRecognizer(tapGesture) 57 | sceneView.addGestureRecognizer(pinchGesture) 58 | 59 | } 60 | 61 | func addSwipeGesture(for direction: UISwipeGestureRecognizerDirection) -> UIGestureRecognizer { 62 | let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(throwObject(_:))) 63 | swipeGesture.direction = direction 64 | swipeGesture.numberOfTouchesRequired = 3 65 | sceneView.addGestureRecognizer(swipeGesture) 66 | return swipeGesture 67 | } 68 | 69 | // MARK: - Gesture Actions 70 | 71 | @objc 72 | func didPinch(_ gesture: UIPinchGestureRecognizer) { 73 | switch gesture.state { 74 | case .changed: 75 | guard let object = selectedObject else { return } 76 | let scaleValue: Float = Float(gesture.scale) 77 | if object.physicsBody == nil { 78 | object.scale = SCNVector3(scaleValue, scaleValue, scaleValue) 79 | } else { 80 | object.changeScaleIfPhysicsBodyIncluded(forSize: CGFloat(scaleValue)) 81 | } 82 | print("state changed") 83 | default: 84 | break 85 | } 86 | } 87 | 88 | @objc 89 | func didPan(_ gesture: ThresholdPanGesture) { 90 | switch gesture.state { 91 | case .began: 92 | // Check for interaction with a new object. 93 | if let object = objectInteracting(with: gesture, in: sceneView) { 94 | trackedObject = object 95 | trackedObject?.physicsBody?.isAffectedByGravity = false 96 | } 97 | 98 | case .changed where gesture.isThresholdExceeded: 99 | guard let object = trackedObject else { return } 100 | let translation = gesture.translation(in: sceneView) 101 | 102 | let currentPosition = currentTrackingPositionPoint ?? CGPoint(sceneView.projectPoint(object.position)) 103 | 104 | // The `currentTrackingPositionPoint` is used to update the `selectedObject` in `updateObjectToCurrentTrackingPosition()`. 105 | currentTrackingPositionPoint = CGPoint(x: currentPosition.x + translation.x, y: currentPosition.y + translation.y) 106 | 107 | currentTrackingPositionVector = SCNVector3.init(Float(object.position.x) + Float(translation.x / 1000), Float(object.position.y) - Float(translation.y / 1000), object.position.z) 108 | 109 | gesture.setTranslation(.zero, in: sceneView) 110 | 111 | case .changed: 112 | // Ignore changes to the pan gesture until the threshold for displacment has been exceeded. 113 | break 114 | 115 | default: 116 | // Clear the current position tracking. 117 | currentTrackingPositionVector = nil 118 | currentTrackingPositionPoint = nil 119 | trackedObject?.physicsBody?.isAffectedByGravity = true 120 | trackedObject = nil 121 | } 122 | } 123 | 124 | /** 125 | If a drag gesture is in progress, update the tracked object's position by 126 | converting the 2D touch location on screen (`currentTrackingPositionVector`) to 127 | 3D world space. 128 | This method is called per frame (via `SCNSceneRendererDelegate` callbacks), 129 | allowing drag gestures to move virtual objects regardless of whether one 130 | drags a finger across the screen or moves the device through space. 131 | - Tag: updateObjectToCurrentTrackingPosition 132 | */ 133 | @objc 134 | func updateObjectToCurrentTrackingPosition() { 135 | guard let object = trackedObject, let positionVector = currentTrackingPositionVector, let positionPoint = currentTrackingPositionPoint else { return } 136 | if object.physicsBody == nil { 137 | translate(object, basedOn: positionPoint, infinitePlane: translateAssumingInfinitePlane) 138 | } else { 139 | object.position = positionVector 140 | } 141 | } 142 | 143 | /// - Tag: didRotate 144 | @objc 145 | func didRotate(_ gesture: UIRotationGestureRecognizer) { 146 | guard gesture.state == .changed else { return } 147 | 148 | /* 149 | - Note: 150 | For looking down on the object (99% of all use cases), we need to subtract the angle. 151 | To make rotation also work correctly when looking from below the object one would have to 152 | flip the sign of the angle depending on whether the object is above or below the camera... 153 | */ 154 | trackedObject?.eulerAngles.y -= Float(gesture.rotation) 155 | 156 | gesture.rotation = 0 157 | } 158 | 159 | @objc 160 | func didTap(_ gesture: UITapGestureRecognizer) { 161 | let touchLocation = gesture.location(in: sceneView) 162 | 163 | if let tappedObject = sceneView.virtualObject(at: touchLocation) { 164 | // Select a new object. 165 | selectedObject = tappedObject 166 | } else if let object = selectedObject { 167 | // Teleport the object to whereever the user touched the screen. 168 | translate(object, basedOn: touchLocation, infinitePlane: false) 169 | } 170 | } 171 | 172 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 173 | // Allow objects to be translated and rotated at the same time. 174 | return true 175 | } 176 | 177 | @objc 178 | func throwObject(_ gesture: UISwipeGestureRecognizer) { 179 | if let _ = selectedObject { 180 | var x = 0 181 | var y = 0 182 | var z = 0 183 | switch gesture.direction { 184 | case .down: 185 | x = 0 186 | z = 5 187 | case .up: 188 | x = 0 189 | z = -5 190 | case .right: 191 | x = 5 192 | z = 0 193 | case .left: 194 | x = -5 195 | z = 0 196 | default: 197 | break 198 | } 199 | //throw the object using the direction and velocity of the swipe gesture 200 | print("\(sceneView.session.currentFrame?.camera.transform)") 201 | let force = simd_make_float4(Float(x), Float(y), Float(z), 0) 202 | let rotatedForce = simd_mul(sceneView.session.currentFrame!.camera.transform, force) 203 | let vectorForce = SCNVector3(x:rotatedForce.x, y:rotatedForce.y, z:rotatedForce.z) 204 | selectedObject?.physicsBody?.applyForce(vectorForce, asImpulse: true) 205 | } 206 | } 207 | 208 | /// A helper method to return the first object that is found under the provided `gesture`s touch locations. 209 | /// - Tag: TouchTesting 210 | private func objectInteracting(with gesture: UIGestureRecognizer, in view: ARSCNView) -> VirtualObject? { 211 | for index in 0.. CGPoint { 244 | let first = CGRect(origin: location(ofTouch: 0, in: view), size: .zero) 245 | 246 | let touchBounds = (1..