diff --git a/SwiftARlps.xcodeproj/project.pbxproj b/SwiftARlps.xcodeproj/project.pbxproj index 316fdb5..fe77248 100644 --- a/SwiftARlps.xcodeproj/project.pbxproj +++ b/SwiftARlps.xcodeproj/project.pbxproj @@ -45,6 +45,10 @@ CE43AEF2E18524F924BE58A5 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8524A246D8DFAA4CD472DE35 /* CoreTelephony.framework */; }; D7D405F240B24C036B07D812 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25E95722BBA12C622919F581 /* CoreText.framework */; }; DD8574D0EC668488A46A15EA /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B74C538658DA39BD2183AAC /* AssetsLibrary.framework */; }; + FEE7A9E11FC87B190019DA72 /* UIDeviceOrientation+CameraOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE7A9E01FC87B190019DA72 /* UIDeviceOrientation+CameraOrientation.swift */; }; + FEE7A9E31FC885B10019DA72 /* SCNVector3+Distance.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE7A9E21FC885B10019DA72 /* SCNVector3+Distance.swift */; }; + FEF69C8D1FC8870D00A62594 /* EmojiFace.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEF69C8C1FC8870D00A62594 /* EmojiFace.swift */; }; + FEF69C8F1FC88AC900A62594 /* String+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEF69C8E1FC88AC900A62594 /* String+Image.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -94,6 +98,10 @@ E80C42879941D13A174F590C /* BuddyBuildSDK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = BuddyBuildSDK.framework; sourceTree = ""; }; EDEAD543F9C5D3E64B9CC319 /* SampleCode.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = SampleCode.xcconfig; path = Configuration/SampleCode.xcconfig; sourceTree = ""; }; FA66759253A43DA05D9DABA5 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; + FEE7A9E01FC87B190019DA72 /* UIDeviceOrientation+CameraOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDeviceOrientation+CameraOrientation.swift"; sourceTree = ""; }; + FEE7A9E21FC885B10019DA72 /* SCNVector3+Distance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SCNVector3+Distance.swift"; sourceTree = ""; }; + FEF69C8C1FC8870D00A62594 /* EmojiFace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiFace.swift; sourceTree = ""; }; + FEF69C8E1FC88AC900A62594 /* String+Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Image.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -183,6 +191,9 @@ 3929C48A1E89EC2C00E00B60 /* SwiftARlps */ = { isa = PBXGroup; children = ( + FEF69C8E1FC88AC900A62594 /* String+Image.swift */, + FEE7A9E01FC87B190019DA72 /* UIDeviceOrientation+CameraOrientation.swift */, + FEE7A9E21FC885B10019DA72 /* SCNVector3+Distance.swift */, 1FB542991FC861A80026CB94 /* SCNNode+Extensions.swift */, 3929C48B1E89EC2C00E00B60 /* AppDelegate.swift */, 3929C48D1E89EC2C00E00B60 /* ViewController.swift */, @@ -228,6 +239,7 @@ B3C1D8BF1FC079560002729A /* Cube.swift */, B399C9A21FC834C700102E39 /* Plane.swift */, B399C9A41FC83CF400102E39 /* Ball.swift */, + FEF69C8C1FC8870D00A62594 /* EmojiFace.swift */, ); path = "Virtual Objects"; sourceTree = ""; @@ -280,7 +292,7 @@ TargetAttributes = { 3929C4871E89EC2C00E00B60 = { CreatedOnToolsVersion = 9.0; - DevelopmentTeam = 729WVZ5N33; + DevelopmentTeam = 53B99NSR52; LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; @@ -326,19 +338,23 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FEF69C8F1FC88AC900A62594 /* String+Image.swift in Sources */, C812D8681F47628300344E73 /* ThresholdPanGesture.swift in Sources */, 11BD822A1F09D750003F11C8 /* ViewController+ObjectSelection.swift in Sources */, 1488323F1EFC378A0043E0AB /* ViewController+Actions.swift in Sources */, 3929C48E1E89EC2C00E00B60 /* ViewController.swift in Sources */, + FEE7A9E31FC885B10019DA72 /* SCNVector3+Distance.swift in Sources */, B3C1D8C01FC079560002729A /* Cube.swift in Sources */, B3F65D9D1FC867D2005F5F43 /* BallCube.swift in Sources */, B399C9A31FC834C700102E39 /* Plane.swift in Sources */, C41366B91EB80DD400EC054A /* StatusViewController.swift in Sources */, B3C1D8C21FC07D130002729A /* Positionable.swift in Sources */, 39C0B95A1E9C6B1F003FCA0C /* Utilities.swift in Sources */, + FEF69C8D1FC8870D00A62594 /* EmojiFace.swift in Sources */, 147062641F06FA790003EE03 /* VirtualObjectSelection.swift in Sources */, B399C9A51FC83CF400102E39 /* Ball.swift in Sources */, C8C12A711F29277B00B011FE /* ViewController+ARSCNViewDelegate.swift in Sources */, + FEE7A9E11FC87B190019DA72 /* UIDeviceOrientation+CameraOrientation.swift in Sources */, 1FB5429A1FC861A90026CB94 /* SCNNode+Extensions.swift in Sources */, C8C2AC521F3A545C00E86BA3 /* VirtualObjectInteraction.swift in Sources */, C46356671EF9CC500080C8D9 /* VirtualObjectARView.swift in Sources */, @@ -483,14 +499,14 @@ CODE_SIGN_IDENTITY = "-"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 729WVZ5N33; + DEVELOPMENT_TEAM = 53B99NSR52; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); INFOPLIST_FILE = "$(SRCROOT)/SwiftARlps/Resources/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.swiftalps.SwiftARlps; + PRODUCT_BUNDLE_IDENTIFIER = ink.jal.SwiftARlps; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Bridging-Header.h"; @@ -507,14 +523,14 @@ CODE_SIGN_IDENTITY = "-"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 729WVZ5N33; + DEVELOPMENT_TEAM = 53B99NSR52; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); INFOPLIST_FILE = "$(SRCROOT)/SwiftARlps/Resources/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.swiftalps.SwiftARlps; + PRODUCT_BUNDLE_IDENTIFIER = ink.jal.SwiftARlps; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Bridging-Header.h"; diff --git a/SwiftARlps/SCNVector3+Distance.swift b/SwiftARlps/SCNVector3+Distance.swift new file mode 100644 index 0000000..d32ef73 --- /dev/null +++ b/SwiftARlps/SCNVector3+Distance.swift @@ -0,0 +1,51 @@ +// +// SCNVector3+Distance.swift +// SwiftARlps +// +// Created by Eran Jalink on 24/11/2017. +// Copyright © 2017 Apple. All rights reserved. +// + +import Foundation +import ARKit + +public extension SCNVector3 { + + /** + Calculates vector length based on Pythagoras theorem + */ + var length:Float { + get { + return sqrtf(x*x + y*y + z*z) + } + } + + func distance(toVector: SCNVector3) -> Float { + return (self - toVector).length + } + + static func positionFromTransform(_ transform: matrix_float4x4) -> SCNVector3 { + return SCNVector3Make(transform.columns.3.x, transform.columns.3.y, transform.columns.3.z) + } + + static func -(left: SCNVector3, right: SCNVector3) -> SCNVector3 { + return SCNVector3Make(left.x - right.x, left.y - right.y, left.z - right.z) + } + + static func center(_ vectors: [SCNVector3]) -> SCNVector3 { + var x: Float = 0 + var y: Float = 0 + var z: Float = 0 + + let size = Float(vectors.count) + vectors.forEach { + x += $0.x + y += $0.y + z += $0.z + } + return SCNVector3Make(x / size, y / size, z / size) + } +} + + + diff --git a/SwiftARlps/String+Image.swift b/SwiftARlps/String+Image.swift new file mode 100644 index 0000000..dc804ba --- /dev/null +++ b/SwiftARlps/String+Image.swift @@ -0,0 +1,23 @@ +// +// String+Image.swift +// SwiftARlps +// +// Created by Eran Jalink on 24/11/2017. +// Copyright © 2017 Apple. All rights reserved. +// + +import UIKit + +extension String { + func emojiToImage() -> UIImage? { + let size = CGSize(width: 30, height: 35) + UIGraphicsBeginImageContextWithOptions(size, false, 0) + UIColor.white.set() + let rect = CGRect(origin: CGPoint(), size: size) + UIRectFill(CGRect(origin: CGPoint(), size: size)) + (self as NSString).draw(in: rect, withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 30)]) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return image + } +} diff --git a/SwiftARlps/UIDeviceOrientation+CameraOrientation.swift b/SwiftARlps/UIDeviceOrientation+CameraOrientation.swift new file mode 100644 index 0000000..38f2f2e --- /dev/null +++ b/SwiftARlps/UIDeviceOrientation+CameraOrientation.swift @@ -0,0 +1,20 @@ +// +// UIDeviceOrientation+CameraOrientation.swift +// SwiftARlps +// +// Created by Eran Jalink on 24/11/2017. +// Copyright © 2017 Apple. All rights reserved. +// + +import UIKit + +extension UIDeviceOrientation { + func cameraOrientation() -> CGImagePropertyOrientation { + switch self { + case .landscapeLeft: return .up + case .landscapeRight: return .down + case .portraitUpsideDown: return .left + default: return .right + } + } +} diff --git a/SwiftARlps/ViewController+Actions.swift b/SwiftARlps/ViewController+Actions.swift index 790b252..6bf0875 100644 --- a/SwiftARlps/ViewController+Actions.swift +++ b/SwiftARlps/ViewController+Actions.swift @@ -1,15 +1,18 @@ /* -See LICENSE folder for this sample’s licensing information. - -Abstract: -UI Actions for the main view controller. -*/ + See LICENSE folder for this sample’s licensing information. + + Abstract: + UI Actions for the main view controller. + */ import UIKit import SceneKit +import Vision extension ViewController: UIGestureRecognizerDelegate { + + enum SegueIdentifier: String { case showObjects } @@ -29,14 +32,49 @@ extension ViewController: UIGestureRecognizerDelegate { func gestureRecognizerShouldBegin(_: UIGestureRecognizer) -> Bool { return virtualObjectList.objects.isEmpty } - + @IBAction func addCube() { guard !addCubeButton.isHidden else { return } - let cube = Cube() - virtualObjectList.add(object: cube) - DispatchQueue.main.async { - self.placeVirtualObject(cube) + guard let frame = self.sceneView.session.currentFrame else { return } + let image = CIImage.init(cvPixelBuffer: frame.capturedImage).oriented(UIDevice.current.orientation.cameraOrientation()) + let facesRequest = VNDetectFaceRectanglesRequest { request, error in + guard error == nil else { return } + var index = 0 + if let resultCount = request.results?.count, resultCount < self.emojiFaces.count { + for i in resultCount.. index { + cube = self.emojiFaces[index] + } + else { + cube = EmojiFace() + self.emojiFaces.append(cube) + } + + let coordinatesFloat = float3(worldCoordinates.x, worldCoordinates.y, worldCoordinates.z) + self.placeVirtualObject(cube, location: coordinatesFloat) + } + index += 1 + } + } + try? VNImageRequestHandler(ciImage: image).perform([facesRequest]) + + + // let cube = Cube() + // virtualObjectList.add(object: cube) + // DispatchQueue.main.async { + // self.placeVirtualObject(cube) + // } } @IBAction func addBallCube() { @@ -56,12 +94,12 @@ extension ViewController: UIGestureRecognizerDelegate { self.placeVirtualObject(sphere) } } - + @IBAction func didTapRedButton() { guard !changeColorToRedButton.isHidden else { return } virtualObjectInteraction.selectedObject?.changeColor(color: UIColor.red) } - + func gestureRecognizer(_: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith _: UIGestureRecognizer) -> Bool { return true } @@ -70,15 +108,15 @@ extension ViewController: UIGestureRecognizerDelegate { func restartExperience() { guard isRestartAvailable, !virtualObjectList.isLoading else { return } isRestartAvailable = false - + statusViewController.cancelAllScheduledMessages() - + virtualObjectList.removeAllObjects() addObjectButton.setImage(#imageLiteral(resourceName: "candle"), for: []) addObjectButton.setImage(#imageLiteral(resourceName: "candle"), for: [.highlighted]) - + resetTracking() - + // Disable restart for a while in order to give the session time to restart. DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { self.isRestartAvailable = true @@ -89,7 +127,7 @@ extension ViewController: UIGestureRecognizerDelegate { extension ViewController: UIPopoverPresentationControllerDelegate { // MARK: - UIPopoverPresentationControllerDelegate - + func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle { return .none } @@ -103,8 +141,8 @@ extension ViewController: UIPopoverPresentationControllerDelegate { } guard let identifier = segue.identifier, - let segueIdentifer = SegueIdentifier(rawValue: identifier), - segueIdentifer == .showObjects else { return } + let segueIdentifer = SegueIdentifier(rawValue: identifier), + segueIdentifer == .showObjects else { return } let objectsViewController = segue.destination as! ModelObjectSelectionViewController objectsViewController.modelObjects = ModelObject.availableObjects @@ -117,4 +155,76 @@ extension ViewController: UIPopoverPresentationControllerDelegate { } } + /// Transform bounding box according to device orientation + /// + /// - Parameter boundingBox: of the face + /// - Returns: transformed bounding box + private func transformBoundingBox(_ boundingBox: CGRect) -> CGRect { + var size: CGSize + var origin: CGPoint + let bounds = sceneView.bounds + + switch UIDevice.current.orientation { + case .landscapeLeft, .landscapeRight: + size = CGSize(width: boundingBox.width * bounds.height, + height: boundingBox.height * bounds.width) + default: + size = CGSize(width: boundingBox.width * bounds.width, + height: boundingBox.height * bounds.height) + } + + switch UIDevice.current.orientation { + case .landscapeLeft: + origin = CGPoint(x: boundingBox.minY * bounds.width, + y: boundingBox.minX * bounds.height) + case .landscapeRight: + origin = CGPoint(x: (1 - boundingBox.maxY) * bounds.width, + y: (1 - boundingBox.maxX) * bounds.height) + case .portraitUpsideDown: + origin = CGPoint(x: (1 - boundingBox.maxX) * bounds.width, + y: boundingBox.minY * bounds.height) + default: + origin = CGPoint(x: boundingBox.minX * bounds.width, + y: (1 - boundingBox.maxY) * bounds.height) + } + + return CGRect(origin: origin, size: size) + } + + /// In order to get stable vectors, we determine multiple coordinates within an interval. + /// + /// - Parameters: + /// - boundingBox: Rect of the face on the screen + /// - Returns: the normalized vector + private func normalizeWorldCoord(_ boundingBox: CGRect) -> SCNVector3? { + + var array: [SCNVector3] = [] + Array(0...2).forEach{_ in + if let position = determineWorldCoord(boundingBox) { + array.append(position) + } + usleep(12000) // .012 seconds + } + + if array.isEmpty { + return nil + } + + return SCNVector3.center(array) + } + + /// Determine the vector from the position on the screen. + /// + /// - Parameter boundingBox: Rect of the face on the screen + /// - Returns: the vector in the sceneView + private func determineWorldCoord(_ boundingBox: CGRect) -> SCNVector3? { + let arHitTestResults = sceneView.hitTest(CGPoint(x: boundingBox.midX, y: boundingBox.midY), types: [.featurePoint]) + + // Filter results that are to close + if let closestResult = arHitTestResults.filter({ $0.distance > 0.10 }).first { + // print("vector distance: \(closestResult.distance)") + return SCNVector3.positionFromTransform(closestResult.worldTransform) + } + return nil + } } diff --git a/SwiftARlps/ViewController+ObjectSelection.swift b/SwiftARlps/ViewController+ObjectSelection.swift index 1c8e0f5..4702698 100644 --- a/SwiftARlps/ViewController+ObjectSelection.swift +++ b/SwiftARlps/ViewController+ObjectSelection.swift @@ -16,14 +16,18 @@ extension ViewController: ModelObjectSelectionViewControllerDelegate { - Tag: PlaceVirtualObject */ - func placeVirtualObject(_ virtualObject: VirtualObject) { + func placeVirtualObject(_ virtualObject: VirtualObject, location: float3? = nil) { guard let cameraTransform = session.currentFrame?.camera.transform, - let focusSquarePosition = focusSquare.lastPosition else { + var focusSquarePosition = focusSquare.lastPosition else { statusViewController.showMessage("CANNOT PLACE OBJECT\nTry moving left or right.") return } virtualObjectInteraction.selectedObject = virtualObject + if let location = location { + focusSquarePosition = location + } + virtualObject.setPosition(focusSquarePosition, relativeTo: cameraTransform, smoothMovement: false) updateQueue.async { diff --git a/SwiftARlps/ViewController.swift b/SwiftARlps/ViewController.swift index 9211f85..f7ae5bb 100644 --- a/SwiftARlps/ViewController.swift +++ b/SwiftARlps/ViewController.swift @@ -31,7 +31,9 @@ class ViewController: UIViewController { var focusSquare = FocusSquare() var planes: [ARPlaneAnchor: Plane] = [:] - + + var emojiFaces:[EmojiFace] = [] + /// The view controller that displays the status and "restart experience" UI. lazy var statusViewController: StatusViewController = { return childViewControllers.lazy.flatMap({ $0 as? StatusViewController }).first! diff --git a/SwiftARlps/Virtual Objects/EmojiFace.swift b/SwiftARlps/Virtual Objects/EmojiFace.swift new file mode 100644 index 0000000..ee06c3d --- /dev/null +++ b/SwiftARlps/Virtual Objects/EmojiFace.swift @@ -0,0 +1,29 @@ +// +// EmojiFace.swift +// SwiftARlps +// +// Created by Eran Jalink on 24/11/2017. +// Copyright © 2017 Apple. All rights reserved. +// + +import SceneKit + +class EmojiFace: SCNNode, Positionable { + var recentModelObjectDistances: [Float] = [] + + let face: SCNBox + let size: CGFloat = 0.1 + override init() { + self.face = SCNBox(width: size, height: size, length: size, chamferRadius: 0) + super.init() + let emoji = "😀".emojiToImage() + let material = getMaterial(for: emoji as Any) + face.materials = [material] + self.geometry = face + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} +