引言
從 Apple 釋出 ARKit
框架起,我就一直想學習並做點好玩的東西,後來就勾搭了滑滑雞大佬來上海 Code 沙龍第八次活動講他做的 ARGitHubCommits,學習了一些 ARKit
的基礎知識,後來持續跟進了一波,看了很多張嘉夫大佬的 ARKit
文章,也看了一些 ARKit
開源的專案。那會微博上有一個 ARKit
不停的彈 Windows 告警對話方塊的動態圖特別火,看到的時候我就想,彈出來的這一堆對話方塊好像一條路徑啊,這也是 Find me 這個 App 最初的靈感來源。
探索
後來就想利用這個特性做一些尋路的方面的探索,最開始是想做在家裡找東西,腦洞如下:
- 在家裡找一個位置固定不變的物品(比如電視機)做為參考點。
- 為每件物品錄製一條路徑到這個參考點。
- 想找某件物品的時候先找到這個參考點,然後就可以通過之前錄製的路徑找到這件物品了。
但是發現有兩個特別大的痛點:
- 東西位置經常會變,路徑也要跟著變。
- 找不到的東西往往是亂放的東西,所以當然也沒有路徑記錄一說。
所以這個腦洞就被我 Pass 了,專案也擱置了一段時間。
然後有一天,我約了朋友去商場吃飯。他先到了店裡,但是我確始終找不到這個店,問了半天才在一個很隱蔽的角落找到了這個店。約完回家之後靈感突發,這個場景完全可以用之前找東西的思路來做啊:
- 先到商場的人找一個好找的位置,比如商場北門。
- 從這個位置開始記錄一條到門店的路徑,並分享給後來的朋友。
- 後來的朋友找到這條路徑的起點,也就是之前說的商場北門,開啟先到的人分享的路徑即可沿著路徑找到約好見面的門店。
關鍵問題:從使用場景來說,沒有痛點。
這也是我今天文章內容的主角 - Find me 實現的功能。
目前這個 App 已經發布上架:Find me,而且程式碼已經開源了,地址在:mmoaay/Findme(喜歡的話記得點個 Star),為什麼選擇開源呢?因為只是一個創意,並沒有太多的技術壁壘,而且目前在技術上確實存在兩個問題:
- 路徑記錄和尋找過程中
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,下面是一些非常重要的使用建議:
- 如果在記錄或者尋路過程中,有新訊息,千萬不要切出去看,畢竟商場內導航也就是 10 分鐘左右的事情,正常晚 10 分鐘回覆訊息也沒事。
- 尋路的人一定要找準路徑初始點!非常重要!這個直接決定了路徑終點位置的準確度。
其他一些比較有趣的技術點
路徑中的箭頭實現
先看一下效果:
這個技術點包含兩個部分:
- 怎麼繪製箭頭?
- 如果讓箭頭指向下一個點的位置?
怎麼繪製箭頭?
首先,我們生成一個如下圖形狀的 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
上,大家有興趣也可以看看張嘉夫大佬的一些教程,質量很高。