從 Apple 發佈 ARKit
框架起,我就一直想學習並作點好玩的東西,後來就勾搭了滑滑雞大佬來上海 Code 沙龍第八次活動講他作的 ARGitHubCommits,學習了一些 ARKit
的基礎知識,後來持續跟進了一波,看了不少張嘉夫大佬的 ARKit
文章,也看了一些 ARKit
開源的項目。那會微博上有一個 ARKit
不停的彈 Windows 告警對話框的動態圖特別火,看到的時候我就想,彈出來的這一堆對話框好像一條路徑啊,這也是 Find me 這個 App 最初的靈感來源。node
後來就想利用這個特性作一些尋路的方面的探索,最開始是想作在家裏找東西,腦洞以下:git
可是發現有兩個特別大的痛點:github
因此這個腦洞就被我 Pass 了,項目也擱置了一段時間。swift
而後有一天,我約了朋友去商場吃飯。他先到了店裏,可是我確始終找不到這個店,問了半天才在一個很隱蔽的角落找到了這個店。約完回家以後靈感突發,這個場景徹底能夠用以前找東西的思路來作啊:bash
關鍵問題:從使用場景來講,沒有痛點。session
這也是我今天文章內容的主角 - Find me 實現的功能。併發
目前這個 App 已經發布上架:Find me,並且代碼已經開源了,地址在:mmoaay/Findme(喜歡的話記得點個 Star),爲何選擇開源呢?由於只是一個創意,並無太多的技術壁壘,並且目前在技術上確實存在兩個問題:app
ARKit
的 Session 不能被打斷,由於恢復以後的虛擬座標會產生極大誤差,致使虛擬路徑進入不可控狀態。ARKit
目前不穩定,虛擬座標系會抖動,這樣就會致使虛擬路徑有偏移,並且距離越遠,誤差越大。產生這兩個問題的緣由主要都在虛擬世界座標上,咱們都知道,ARKit
初始化的時候,會基於你當前位置爲世界原點,創建了一個虛擬世界座標系:框架
因此,Find me 記錄並分享出來的路徑,對 ARKit
虛擬世界的座標系有兩個基本要求:學習
對於這兩個問題,我也作了一些優化。
思路很簡單:根據定位將路徑分段記錄,並修正虛擬路徑。至關於給虛擬路徑加一個現實世界的座標修正。
實踐以後發現:定位比 ARKit
還不許,尤爲在商場內,基於 WiFi 的定位基本上能讓你的位置處處跳。Pass!
這個方案的思路是:根據你在虛擬世界移動的距離分段記錄路徑。
實踐以後問題也來了:ARKit
的初始化太慢了,在個人 iPhone 7 上須要的時間足足有 3 秒…並且 ARKit
的 Seesion 還不能併發,由於攝像頭只有一個,只有上一個結束了,下一個才能開始,最後發現路徑根本無法記錄…
ARKit
使用優化設置 ARWorldTrackingConfiguration
的 worldAlignment
爲 .gravityAndHeading
。
首先咱們來看一下 WorldAlignment
類型:
/**
Enum constants for indicating the world alignment.
*/
@available(iOS 11.0, *)
public enum WorldAlignment : Int {
/** Aligns the world with gravity that is defined by vector (0, -1, 0). */
case gravity
/** Aligns the world with gravity that is defined by the vector (0, -1, 0)
and heading (w.r.t. True North) that is given by the vector (0, 0, -1). */
case gravityAndHeading
/** Aligns the world with the camera’s orientation. */
case camera
}
複製代碼
.gravity
:只有重力,也就是座標系的垂直方向和真實世界一致,可是水平方向不定。.gravityAndHeading
:重力和指北,垂直和水平方向都和真實世界一致。.camera
:攝像頭方向,也就是座標系和手機保持一致。因此這就是咱們選擇 .gravityAndHeading
的緣由。這樣一來,根據路徑找人的那我的就只須要找到路徑起始點的真實位置便可,手機的方向就不重要了,極大下降的使用門檻。
這個優化的內容是:分享路徑的時候提供一張起始點的照片。這樣拿到路徑的人就拿圖片和本身所在的場景作一個大體的比對來肯定分享路徑的人當時的位置,看一下使用效果:
這張照片咱們直接用 ARKit
的 sceneView.session.currentFrame
屬性獲取,以下:
if let currentFrame = sceneView.session.currentFrame, let image = UIImage(pixelBuffer: currentFrame.capturedImage, context:CIContext()) {
}
複製代碼
基於上面說的狀況,若是你在平常生活中確實想使用 Find me,下面是一些很是重要的使用建議:
先看一下效果:
這個技術點包含兩個部分:
首先,咱們生成一個以下圖形狀的 UIBezierPath
:
代碼以下:
private static func vertexCoordinates() -> [CGPoint] {
return [CGPoint(x: 0, y: 0),
CGPoint(x: 20, y: 0),
CGPoint(x: 20, y: 10),
CGPoint(x: 10, y: 10),
CGPoint(x: 10, y: 20),
CGPoint(x: 0, y: 20)
]
}
private static func arrowPath() -> UIBezierPath {
let path = UIBezierPath()
let points = NodeUtil.vertexCoordinates()
var count = 0
for point in points {
if 0 == count {
path.move(to: point)
} else {
path.addLine(to: point)
}
count += 1
}
path.close()
return path
}
複製代碼
而後用下面的代碼生成 SCNNode
:
let path = NodeUtil.arrowPath()
let shape = SCNShape(path: path, extrusionDepth: 2)
let node = SCNNode(geometry: shape)
複製代碼
這樣一個箭頭節點就生成了。
其實就是要把這個箭頭旋轉必定的角度。這裏涉及到一個數學知識:根據兩個點算它們的連線與某個座標軸的角度。我數學很差,原理就不講了,主要講一下 SceneKit
中如何旋轉 SCNNode
:
首先咱們須要兩個點,當前點和上一次的點,因此咱們經過一個 SCNVector3
類型的 last
變量來記錄上一次的點,SCNVector3
包含 x、y、z 三個屬性,分別對應了 x、y、z 軸的值。
而後根據當前點和上一次點的位置獲得角度,用 SCNAction.rotateBy
來生成一個旋轉動做,再使用 SCNNode
的 runAction
方法來執行這個動做便可。
最終代碼以下:
node.runAction(SCNAction.rotateBy(x: CGFloat(Float.pi/2.0*3.0), y:CGFloat(Float.pi/4.0+atan2(current.x-last.x, current.z-last.z)), z: 0.0, duration: 0.0))
複製代碼
Find me 目前分享路徑採用的方式是文件分享,分享出去採用的是 UIDocumentInteractionController
,接受別人分享的路徑主要經過 func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
回調獲取,這裏它會經過 url
參數返回分享文件的路徑,這裏有個點,若是你直接打開這個文件,會發現這個文件並不存在…
解決方法比較有趣:經過這個路徑把文件拷貝到另一個路徑,在打開這個文件就能夠了。
ARKit
作爲 Apple 新發布的框架,後期必定會進行更深刻的優化,因此 Find me 路徑的準確度將來仍是很值得期待的,固然我也會對 Find me 作持續的改進,歡迎你們關注。
另外我的感受 ARKit
框架的學習門檻確實不高,主要門檻反而在 SceneKit
或者 SpriteKit
上,你們有興趣也能夠看看張嘉夫大佬的一些教程,質量很高。