网站上线前准备,医疗网站建设基本流程图,九江企业网站制作,抄底券网站怎么做的ARWorldMap 用于存储 ARSession 检测扫描到的空间信息数据#xff0c;包括地标#xff08;Landmark#xff09;、特征点#xff08;Feature Point#xff09;、平面#xff08;Plane#xff09;等#xff0c;以及使用者的操作信息#xff0c;如使用者添加的 ARAnchor … ARWorldMap 用于存储 ARSession 检测扫描到的空间信息数据包括地标Landmark、特征点Feature Point、平面Plane等以及使用者的操作信息如使用者添加的 ARAnchor 和开发者自定义的一些信息。ARWorldMap 可以看作 ARSession 运行时的一次状态快照。 在技术上每个具备世界跟踪的 ARSession 都会时刻维护一个内部的世界地图internal world mapARKit 正是利用这个地图定位跟踪用户设备的姿态利用 getCurrent WorldMapcompletionHandler方法获取的 ARWroldMap只是特定时刻内部世界地图的一个快照。
ARWorldMap 概述 持久化地存储应用进程数据ARKit 提供了 ARWorldMap 功能ARWorldMap 本质是将 AR 场景状态信息转换为可存储可传输的形式即序列化保存到文件系统或者数据库中当使用者再次加载这些景状态信息后即可恢复应用进程。ARWorldMap 不仅保存了应用进程状态信息还保存了场景特征点云息在使用者再次加载这些状态数据后ARKit 可通过保存的特征点云信息与当前用户摄像头获取的特点云信息进行对比匹配从而更新当前用户的坐标确保两个坐标系的匹配。
存储与加载 ARWorldMap 存储 ARWorldMap最重要的是从 ARSession 中获取场景的 ARWorldMap 并序列化之然后保件系统中。加载 ARWorldMap 则首先要从文件系统中获取ARWorldMap 并反序列化之然后利用这个ARWorldMap 重启 ARSession。存储与加载 ARWorldMap 完整代码如下所示稍后我们将对代码中所用技术进行详细解析。
//
// ARWorldMapSaveAndLoad.swift
// ARKitDeamo
//
// Created by zhaoquan du on 2024/2/20.
//import SwiftUI
import ARKit
import RealityKit
import Combinestruct ARWorldMapSaveAndLoad: View {var viewModel: ViewModel ViewModel()var body: some View {ARWorldMapSaveAndLoadContainer(viewModel: viewModel).overlay(VStack{Spacer()HStack{Button(action: {loadWorldMap()}) {Text(加载信息).frame(width:150,height:50).font(.system(size: 17)).foregroundColor(.black).background(Color.white).opacity(0.6)}.cornerRadius(10)Button(action: {saveWorldMap()}) {Text(保存信息).frame(width:150,height:50).font(.system(size: 17)).foregroundColor(.black).background(Color.white).opacity(0.6)}.cornerRadius(10)}Spacer().frame(height: 40)}).edgesIgnoringSafeArea(.all).navigationTitle(保存与加载ARWorldMap)}var mapSaveURL: URL {do {return try FileManager.default.url(for: .documentDirectory,in: .userDomainMask,appropriateFor: nil,create: true).appendingPathComponent(arworldmap.arexperience)} catch {fatalError(获取路径出错: \(error.localizedDescription))}}()func saveWorldMap() {print(save:\(String(describing: viewModel.arView)))self.viewModel.arView?.session.getCurrentWorldMap(completionHandler: { loadWorld, error inguard let worldMap loadWorld else {print(当前无法获取ARWorldMap:\(error!.localizedDescription))return}do {let data try NSKeyedArchiver.archivedData(withRootObject: worldMap, requiringSecureCoding: true)try data.write(to: mapSaveURL, options: [.atomic])print(ARWorldMap保存成功)} catch {fatalError(无法保存ARWorldMap: \(error.localizedDescription))}})}func loadWorldMap() {print(load:\(String(describing: viewModel.arView)))guard let data try? Data(contentsOf: mapSaveURL) else {print(load world map faile)return}var worldMap: ARWorldMap?do {worldMap try NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: data)} catch let error {print(ARWorldMap文件格式不正确:\(error))}guard let worldMap worldMap else {print(无法解压ARWorldMap)return}let config ARWorldTrackingConfiguration()config.planeDetection .horizontalconfig.initialWorldMap worldMapself.viewModel.arView?.session.run(config,options: [.resetTracking, .removeExistingAnchors])}class ViewModel: NSObject,ARSessionDelegate{var arView: ARView? nilvar planeEntity : ModelEntity? nilvar raycastResult : ARRaycastResult?var isPlaced falsevar robotAnchor: AnchorEntity?let robotAnchorName drummerRobotvar planeAnchor AnchorEntity()func createPlane() {guard let arView arView else {return}if let an arView.scene.anchors.first(where: { an inan.name setModelPlane}){arView.scene.anchors.remove(an)}do {let planeMesh MeshResource.generatePlane(width: 0.15, depth: 0.15)var planeMaterial SimpleMaterial(color: SimpleMaterial.Color.red, isMetallic: false)planeMaterial.color try SimpleMaterial.BaseColor(tint:UIColor.yellow.withAlphaComponent(0.9999), texture: MaterialParameters.Texture(TextureResource.load(named: AR_Placement_Indicator)))planeEntity ModelEntity(mesh: planeMesh, materials: [planeMaterial])planeAnchor AnchorEntity(plane: .horizontal)planeAnchor.addChild(planeEntity!)planeAnchor.name setModelPlanearView.scene.addAnchor(planeAnchor)} catch let error {print(加载文件失败:\(error))}}func setupGesture(){let tap UITapGestureRecognizer(target: self, action: #selector(handleTap))self.arView?.addGestureRecognizer(tap)}objc func handleTap(sender: UITapGestureRecognizer){sender.isEnabled falsesender.removeTarget(nil, action: nil)isPlaced truelet anchor ARAnchor(name: robotAnchorName, transform: raycastResult?.worldTransform ?? simd_float4x4())self.arView?.session.add(anchor: anchor)robotAnchor AnchorEntity(anchor: anchor)do {let robot try ModelEntity.load(named: toy_drummer)robotAnchor?.addChild(robot)robot.scale [0.01,0.01,0.01]self.arView?.scene.addAnchor(robotAnchor!)print(Total animation count : \(robot.availableAnimations.count))robot.playAnimation(robot.availableAnimations[0].repeat())} catch {print(找不到USDZ文件)}// var cancellable: Cancellable?
// cancellable ModelEntity.loadModelAsync(named: toy_drummer.usdz)
// .sink(receiveCompletion: { error in
// print(laod error:\(error))
// cancellable?.cancel()
// }, receiveValue: {[weak self] model in
// guard let robotAnchor self?.robotAnchor else {
// return
// }
// robotAnchor.addChild(model)
// model.scale [0.01,0.01,0.01]
// self?.arView?.scene.addAnchor(robotAnchor)
// //用异步方法加载模型开启骨骼动画会crash不知到是啥原因
// //model.playAnimation(model.availableAnimations[0].repeat())
// cancellable?.cancel()
// })planeEntity?.removeFromParent()planeEntity nil}func session(_ session: ARSession, didUpdate frame: ARFrame) {guard !isPlaced, let arView arView else{return}//射线检测guard let result arView.raycast(from: arView.center, allowing: .estimatedPlane, alignment: .horizontal).first else {return}raycastResult resultplaneEntity?.setTransformMatrix(result.worldTransform, relativeTo: nil)}func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {guard !anchors.isEmpty,robotAnchor nil else {return}var panchor: ARAnchor? nilfor anchor in anchors {if anchor.name robotAnchorName {panchor anchorbreak}}guard let pAnchor panchor else {return}//放置虚拟元素robotAnchor AnchorEntity(anchor: pAnchor)do {let robot try ModelEntity.load(named: toy_drummer)robotAnchor?.addChild(robot)robot.scale [0.01,0.01,0.01]self.arView?.scene.addAnchor(robotAnchor!)print(Total animation count : \(robot.availableAnimations.count))robot.playAnimation(robot.availableAnimations[0].repeat())} catch {print(找不到USDZ文件)}isPlaced trueplaneEntity?.removeFromParent()planeEntity nilprint(加载模型成功)}}
}struct ARWorldMapSaveAndLoadContainer: UIViewRepresentable {var viewModel: ARWorldMapSaveAndLoad.ViewModelfunc makeUIView(context: Context) - some ARView {let arView ARView(frame: .zero)return arView}func updateUIView(_ uiView: UIViewType, context: Context) {let config ARWorldTrackingConfiguration()config.planeDetection .horizontaluiView.session.run(config)viewModel.arView uiViewuiView.session.delegate viewModelviewModel.createPlane()viewModel.setupGesture()}}
#Preview {ARWorldMapSaveAndLoad()
}代码实现的功能如下
1进行平面检测在检测到可用平面时实例化一个指示图标用于指示放置位置。
2添加屏幕单击手势在平面可用时通过单击屏幕会在指示图标位置放置虛拟机器人模型。
3 当用户单击“保存AR信息”按钮时会从当前 ARSesion 中获取 ARWorldMap 并序列化之然后保存到文件系统中。
4当用户单击“加载AR信息”按钮时会从文件系统中加载ARWorldMap 并反序列化之然后利用该ARWorldMap 重启 ARSession。 在第2项功能中即 bandleTap方法中的代码我们首先将屏幕单击手势禁用以防止添加多个机器人模型然后禁止显示指示图标。随后利用命申点的坐标生成了一个ARAnchor并将其添加到 ARSession中注意这里设置了 ARAnchor 的名字name属性这步很关键因为后续我们需要利用该ARAnchor 的名字来恢复虚拟元素。后续代码是使用异步方式加载机器人模型不赘述。 在第3项功能中即 saveAR WorldMap方法中代码首先使用 getCurrent WorldMap方法从 ARSession中获取 ARWorldVap在闭包中使用 let data try NSKeyedArchiver. archivedData with RootObject: maprequiringSecureCoding: true语句对获取的ARWorldMap 进行序列化然后使用 try data. write tomapSaveURL. options .atomic方法将序列化后的 ARWorldMap 写人到文件系统中。 在第4项功能中即 loadARWorldMap方法中代码首先从文件系统中读取存储的 ARWorldMap文件并使用 let worldMap try NSKeyedUnarchiver. unarchivedObject ofClass: AR WorldMap. selffrom:data语句将其反序列化。在得到反序列化后的ARWorldMap 后就可以利用其作为配置文件的initialWorldMap 属性重启 ARSession当用户设备所在的物理环境与 ARWorldMap 保存时的物理环境一致时即环境特征点信息匹配时ARKit 就会校正用户设备坐标信息将当前用户设备的坐标信息与ARWorldMap 中存储的用户设备坐标信息关联起来并恢复相应的ARAnchor 信息这时恢复的ARAnchor 姿态与ARWorldMap 中存储的姿态就是一致的即ARAnchor 在物理环境中的位置与方向是一致的这就达到了应用进程数据存储与加载的目的。 正如前文所述ARWorldMap 并不会存储虚拟元素本身因此需要手动恢复虚拟元素因为虚拟元素总是与ARAnchor 关联利用 ARAnchor 的名字name属性我们就可以恢复关联的虚拟元素。在代码中session_didAdd方法就用于恢复关联的虚拟元素在该方法中通过ARAnchor.name 进行ARAnchor的对比如果名字一样且当前没有加载机器人模型则使用异步方法加载之。通过这种方式我们就可以逐一地恢复所有的虚拟元素从而恢复整个场景。 运行案例在检测到的平面上添加虚拟元素后单击“保存地图”按钮保存 AR WorldMap稍后单击“加载地图”按钮或者关闭应用在重新运行后单击“加载地图”按钮可以看到虚拟机器人模型会出现在物理世界中的固定位置如图所示。 事实上在将 ARWorldMap 设置 ARWorldTrackingConfiguration. initialWorldMap 属性启动ARSession 时ARKit 会进入重定位relocalize过程在这个过程中ARKit 会尝试将当前设备摄像头采集的环境信息与 ARWorldMap 中存储的环境特征信息进行匹配。因此保持当前设备姿态与存储ARWorldMap 时的设备姿态一致时即提高环境特征点匹配成功率可以更快速地重定位。
具体代码地址GitHub - duzhaoquan/ARkitDemo