从 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
上,你们有兴趣也能够看看张嘉夫大佬的一些教程,质量很高。