27-《ARKit by Tutorials》读书笔记6: AR持久化与共享

ARKit文章目录

本文是Ray Wenderlich上《ARKit by Tutorials》2.0 版新增章节的读书笔记,主要讲内容概要和读后感  

成品效果

本章的成果是做一个 AR 绘图 app,并可以保存并分享给其他人。

世界追踪和场景持久化

ARKit 依靠 ARWorldMap 来提供数据持久化,其中 ARSession 起到了重要作用。

向 ARSession 中添加 ARAnchor 对象时,它既是 app 中的虚拟物体,也是真实世界中的特征。

放置锚点

只有两个步骤:

  • renderer(_:willRenderScene:atTime:):中创建并添加锚点ARLineAnchor
  • renderer(_:didAdd:for:)中取出锚点,并根据锚点创建节点SCNLineNode
// 添加锚点的代码
func addLineAnchorForObject(sourcePoint: SCNVector3?,
                       destinationPoint: SCNVector3?) {
    // 获取命中测试的第一个结果
    guard let hitTestResult = sceneView
        .hitTest(self.viewCenter!, types: [.existingPlaneUsingGeometry,
                                           .estimatedHorizontalPlane])
        .first
        else { return }
    // 创建 ARLineAnchor 锚点
    currentLineAnchorName = "virtualObject\(count)"
    count = count+1
    let lineAnchor = ARLineAnchor(name: currentLineAnchorName!,
                             transform: hitTestResult.worldTransform,
                           sourcePoint: sourcePoint,
                      destinationPoint: destinationPoint)
    // 添加到 session 中
    sceneView.session.add(anchor: lineAnchor)
    lineObjectAnchors.append(lineAnchor)
}

保存世界地图

世界地图的状态,可以从session(_ session: ARSession, didUpdate frame: ARFrame)中的frame.worldMappingStatus来判断。

func session(_ session: ARSession, didUpdate frame: ARFrame) {
  switch frame.worldMappingStatus {
  case .extending, .mapped:
    // 可用状态
  default:
}

世界地图的实际获取是从 session 中获取的,同时还会保存一张截图

sceneView.session.getCurrentWorldMap { worldMap, error in
  // 取值
  guard let map = worldMap else {
    return
  }

  // 配上一张截图
  guard let snapshotAnchor = SnapshotAnchor(capturing: self.sceneView) else {
    fatalError("Can't take snapshot")
  }
  map.anchors.append(snapshotAnchor)

  do {
    // 归档保存
    let data = try NSKeyedArchiver.archivedData(withRootObject: map,
                                                requiringSecureCoding: true)
    try data.write(to: self.mapSaveURL, options: [.atomic])
  } catch {
    fatalError("Can't save map: \(error.localizedDescription)")
  }
}

加载和恢复

取出保存的世界地图

// 反归档
guard let worldMap =
      try NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self,
                                             from: data)
    else {
      fatalError("No ARWorldMap in archive.")
    }
 // 取出截图
if let snapshotData = worldMap.snapshotAnchor?.imageData,
  let snapshot = UIImage(data: snapshotData) {

} else {
  print("No snapshot image in world map")
}

// 删除截图
worldMap.anchors.removeAll(where: { $0 is SnapshotAnchor })

// 加载使用
let configuration = self.defaultConfiguration
configuration.initialWorldMap = worldMap
sceneView.session.run(configuration,
                      options: [.resetTracking, .removeExistingAnchors])

共享 AR 体验

多人 AR 依赖的是苹果的MultipeerConnectivity框架。这个框架使用 Wifi 和蓝牙来传输数据。你需要一个MCSession对象来广播自己,发现其他设备,及连接到其他 peer 上

import MultipeerConnectivity
class PeerSession: NSObject {
  // 用来沟通的 ID,及协议serviceType
  private let peerID = MCPeerID(displayName: UIDevice.current.name)
  static let serviceType = "arsketchsession"
  // MCSession对象,用来支持多方数据传输
  private(set) var mcSession: MCSession!
  // MCAdvertiserAssistant对象,用来告诉其他 peer,你的 app 可以加入一个多方会话网络中。它还自带了一个标准用户界面,用来接受别人的会话邀请或者主动邀请别人进入会话
  private var advertiserAssistant: MCAdvertiserAssistant!
  // 处理接收到的数据
  private let receivedDataHandler: (Data, MCPeerID) -> Void
 }

建立 peer 连接

代理方法

建立连接,传输数据需要在代理方法中处理,虽然MCAdvertiserAssistantDelegate中的方法不需要我们实现,但MCSessionDelegate中还是需要处理的:
``swift // 当附近 session 中的 peer 状态发生改变时调用。状态变为 MCSessionState.connected 或者 MCSessionState.notConnected时调用 func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { } // 当自己收到 session 中的其他 peer 发来的NSData`数据时调用:

top Created with Sketch.