姓名 連嘉瑋 學(xué)號 16040120089
轉(zhuǎn)自:畫渣程序猿mmoaay 有刪節(jié)
【嵌牛導(dǎo)讀】:世界上七十多億人,有的時候卻只需要找到那一個。
【嵌牛鼻子】:ARKit
【嵌牛提問】:找一個人需要多久?
【嵌牛正文】:
引言
從 Apple 發(fā)布 ARKit 框架起,我就一直想學(xué)習(xí)并做點(diǎn)好玩的東西,后來就勾搭了滑滑雞大佬來上海 Code<T> 沙龍第八次活動講他做的 ARGitHubCommits,學(xué)習(xí)了一些 ARKit 的基礎(chǔ)知識,后來持續(xù)跟進(jìn)了一波,看了很多張嘉夫大佬的 ARKit 文章,也看了一些 ARKit 開源的項(xiàng)目。那會微博上有一個 ARKit 不停的彈 Windows 告警對話框的動態(tài)圖特別火,看到的時候我就想,彈出來的這一堆對話框好像一條路徑啊,這也是 Find me 這個 App 最初的靈感來源。
探索
后來就想利用這個特性做一些尋路的方面的探索,最開始是想做在家里找東西,腦洞如下:
在家里找一個位置固定不變的物品(比如電視機(jī))做為參考點(diǎn)。
為每件物品錄制一條路徑到這個參考點(diǎn)。
想找某件物品的時候先找到這個參考點(diǎn),然后就可以通過之前錄制的路徑找到這件物品了。
但是發(fā)現(xiàn)有兩個特別大的痛點(diǎn):
東西位置經(jīng)常會變,路徑也要跟著變。
找不到的東西往往是亂放的東西,所以當(dāng)然也沒有路徑記錄一說。
所以這個腦洞就被我 Pass 了,項(xiàng)目也擱置了一段時間。
然后有一天,我約了朋友去商場吃飯。他先到了店里,但是我確始終找不到這個店,問了半天才在一個很隱蔽的角落找到了這個店。約完回家之后靈感突發(fā),這個場景完全可以用之前找東西的思路來做?。?/p>
先到商場的人找一個好找的位置,比如商場北門。
從這個位置開始記錄一條到門店的路徑,并分享給后來的朋友。
后來的朋友找到這條路徑的起點(diǎn),也就是之前說的商場北門,打開先到的人分享的路徑即可沿著路徑找到約好見面的門店。
關(guān)鍵問題:從使用場景來說,沒有痛點(diǎn)。
這也是我今天文章內(nèi)容的主角 - Find me 實(shí)現(xiàn)的功能。
目前這個 App 已經(jīng)發(fā)布上架:Find me,而且代碼已經(jīng)開源了,地址在:mmoaay/Findme(喜歡的話記得點(diǎn)個 Star),為什么選擇開源呢?因?yàn)橹皇且粋€創(chuàng)意,并沒有太多的技術(shù)壁壘,而且目前在技術(shù)上確實(shí)存在兩個問題:
路徑記錄和尋找過程中 ARKit 的 Session 不能被打斷,因?yàn)榛謴?fù)之后的虛擬坐標(biāo)會產(chǎn)生極大偏差,導(dǎo)致虛擬路徑進(jìn)入不可控狀態(tài)。
ARKit 目前不穩(wěn)定,虛擬坐標(biāo)系會抖動,這樣就會導(dǎo)致虛擬路徑有偏移,而且距離越遠(yuǎn),偏差越大。
產(chǎn)生這兩個問題的原因主要都在虛擬世界坐標(biāo)上,我們都知道,ARKit 初始化的時候,會基于你當(dāng)前位置為世界原點(diǎn),建立了一個虛擬世界坐標(biāo)系:
所以,F(xiàn)ind me 記錄并分享出來的路徑,對 ARKit 虛擬世界的坐標(biāo)系有兩個基本要求:
記錄路徑和根據(jù)路徑找人時虛擬世界的原點(diǎn)一定要映射到現(xiàn)實(shí)世界中的同一個點(diǎn),也就是分享和尋找的起點(diǎn)位置要是同一個。
虛擬世界的水平垂直方向要和現(xiàn)實(shí)世界一樣。
優(yōu)化
對于這兩個問題,我也做了一些優(yōu)化。
目前失敗的兩個優(yōu)化
基于定位優(yōu)化
思路很簡單:根據(jù)定位將路徑分段記錄,并修正虛擬路徑。相當(dāng)于給虛擬路徑加一個現(xiàn)實(shí)世界的坐標(biāo)修正。
實(shí)踐之后發(fā)現(xiàn):定位比 ARKit 還不準(zhǔn),尤其在商場內(nèi),基于 WiFi 的定位基本上能讓你的位置到處跳。Pass!
基于距離優(yōu)化
這個方案的思路是:根據(jù)你在虛擬世界移動的距離分段記錄路徑。
實(shí)踐之后問題也來了:ARKit 的初始化太慢了,在我的 iPhone 7 上需要的時間足足有 3 秒…而且 ARKit 的 Seesion 還不能并發(fā),因?yàn)閿z像頭只有一個,只有上一個結(jié)束了,下一個才能開始,最后發(fā)現(xiàn)路徑根本沒法記錄…
成功優(yōu)化
ARKit 使用優(yōu)化
設(shè)置 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:只有重力,也就是坐標(biāo)系的垂直方向和真實(shí)世界一致,但是水平方向不定。
.gravityAndHeading:重力和指北,垂直和水平方向都和真實(shí)世界一致。
.camera:攝像頭方向,也就是坐標(biāo)系和手機(jī)保持一致。
所以這就是我們選擇 .gravityAndHeading 的原因。這樣一來,根據(jù)路徑找人的那個人就只需要找到路徑起始點(diǎn)的真實(shí)位置即可,手機(jī)的方向就不重要了,極大降低的使用門檻。
提供路徑起始點(diǎn)圖片
這個優(yōu)化的內(nèi)容是:分享路徑的時候提供一張起始點(diǎn)的照片。這樣拿到路徑的人就拿圖片和自己所在的場景做一個大致的比對來確定分享路徑的人當(dāng)時的位置,看一下使用效果:
這張照片我們直接用 ARKit 的 sceneView.session.currentFrame 屬性獲取,如下:
if let currentFrame = sceneView.session.currentFrame, let image = UIImage(pixelBuffer: currentFrame.capturedImage, context:CIContext()) {
}
使用建議
基于上面說的情況,如果你在日常生活中確實(shí)想使用 Find me,下面是一些非常重要的使用建議:
如果在記錄或者尋路過程中,有新消息,千萬不要切出去看,畢竟商場內(nèi)導(dǎo)航也就是 10 分鐘左右的事情,正常晚 10 分鐘回復(fù)消息也沒事。
尋路的人一定要找準(zhǔn)路徑初始點(diǎn)!非常重要!這個直接決定了路徑終點(diǎn)位置的準(zhǔn)確度。
其他一些比較有趣的技術(shù)點(diǎn)
路徑中的箭頭實(shí)現(xiàn)
先看一下效果:
這個技術(shù)點(diǎn)包含兩個部分:
怎么繪制箭頭?
如果讓箭頭指向下一個點(diǎn)的位置?
怎么繪制箭頭?
首先,我們生成一個如下圖形狀的 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)
這樣一個箭頭節(jié)點(diǎn)就生成了。
如果讓箭頭指向下一個點(diǎn)的位置?
其實(shí)就是要把這個箭頭旋轉(zhuǎn)一定的角度。這里涉及到一個數(shù)學(xué)知識:根據(jù)兩個點(diǎn)算它們的連線與某個坐標(biāo)軸的角度。我數(shù)學(xué)不好,原理就不講了,主要講一下 SceneKit 中如何旋轉(zhuǎn) SCNNode:
首先我們需要兩個點(diǎn),當(dāng)前點(diǎn)和上一次的點(diǎn),所以我們通過一個 SCNVector3 類型的 last 變量來記錄上一次的點(diǎn),SCNVector3 包含 x、y、z 三個屬性,分別對應(yīng)了 x、y、z 軸的值。
然后根據(jù)當(dāng)前點(diǎn)和上一次點(diǎn)的位置得到角度,用 SCNAction.rotateBy 來生成一個旋轉(zhuǎn)動作,再使用 SCNNode 的 runAction 方法來執(zhí)行這個動作即可。
最終代碼如下:
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 回調(diào)獲取,這里它會通過 url 參數(shù)返回分享文件的路徑,這里有個點(diǎn),如果你直接打開這個文件,會發(fā)現(xiàn)這個文件并不存在…
解決方法比較有趣:通過這個路徑把文件拷貝到另外一個路徑,在打開這個文件就可以了。
總結(jié)
ARKit 做為 Apple 新發(fā)布的框架,后期一定會進(jìn)行更深入的優(yōu)化,所以 Find me 路徑的準(zhǔn)確度未來還是很值得期待的,當(dāng)然我也會對 Find me 做持續(xù)的改進(jìn),歡迎大家關(guān)注。
另外個人感覺 ARKit 框架的學(xué)習(xí)門檻確實(shí)不高,主要門檻反而在 SceneKit 或者 SpriteKit 上,大家有興趣也可以看看張嘉夫大佬的一些教程,質(zhì)量很高。