【iOS ARKit】RealityKit 同步机制

news2024/11/17 13:58:06

     协作 Session 可以很方便地实现多用户之间的AR体验实时共享,但开发者需要自行负责并确保AR场景的完整性,自行负责虚拟物体的创建与销毁。为简化同步操作,RealityKit 内建了同步机制,RealityKit 同步机制基于 Multipeer Connectivity,当设置好 MultipeerConnectivityService 属性之后,RealityKit 会自动在参与者之间同步实体对象(Entity)。

RealityKit 同步服务机制

      在 RealityKit 中,组成场景(Scene)的基本元素是实体(Entity),实体由其所挂载的组件(Component)定义外观和行为,RealityKit 所有的实体类都继承自 Entity 基类,如 AnchorEntity、ModelEntity,Entity 基类包含了两个组件:Transtorm 组件和 Synchronization 组件,Transform 组件用于空间定位,而Synchronization 组件用于同步。因此,RealityKit 中所有的实体对象都默认带有 Synchronization 组件,即都可以通过网络进行同步,这也是 RealityKit 同步的技术基础。

    虽然 RealityKit 网络数据传输仍然依赖于 Multipeer Connectivity,但相对于协作 Session,在 RealityKit中,开发人员不再需要自行处理数据的发送与接收处理工作,RealityKit 会自动进行相关操作,从而大大简化了开发流程。

    使用 RealityKit 的同步服务功能只需要两步操作:(1)使用 Multipeer Connectivity 设置好 MCSession,并生成一个 MultipeerConnectivityService 对象。(2) 将生成的 MultipeerConnectivityService 对象赋给 ARView.scene 的 synchronizationService 属性,场景中所有的实体对象都将继承该值。在进行这两步操作之后,后续的所有同步操作完全由 RealityKit 自动处理,使用 RealtiyKit 同步服务的完整代码如下所示,稍后我们会对代码进行详细解析。

//
//  SyncARSession.swift
//  ARKitDeamo
//
//  Created by zhaoquan du on 2024/2/28.
//

import SwiftUI
import ARKit
import RealityKit
import MultipeerConnectivity

struct SyncARSession: View {
    static var arView: ARView!
    static var multipeerSession: MultipeerSession?
    var body: some View {
        SnycARSessionContent()
            .onDisappear {
                SyncARSession.arView.session.delegate = nil
                SyncARSession.arView.session.pause()
                SyncARSession.arView = nil
                SyncARSession.multipeerSession?.endConnect()
                SyncARSession.multipeerSession = nil
                print("SyncARSession onDisappear")
            }
            .edgesIgnoringSafeArea(.all).navigationTitle("ARSession同步")
    }
   
}

struct SnycARSessionContent: UIViewRepresentable {

    func makeUIView(context: Context) -> some ARView {
        let arView = ARView(frame: .zero)
        
        
        arView.automaticallyConfigureSession = false
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = .horizontal
        config.isCollaborationEnabled = true
        
        arView.session.run(config,options: [.resetTracking,.removeExistingAnchors])
        arView.session.delegate = context.coordinator
        
        SyncARSession.arView = arView
        context.coordinator.createPlane()
        context.coordinator.addGesture()
        
        
        
        return arView
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    class Coordinator: NSObject,ARSessionDelegate {
        
        var arView: ARView? {
            return SyncARSession.arView
        }
        var multipeerSession: MultipeerSession? {
            return SyncARSession.multipeerSession
        }
        var planeEntity : ModelEntity? = nil
        var raycastResult : ARRaycastResult?
        
        
        func createPlane(){
            SyncARSession.multipeerSession = MultipeerSession(serviceType: "sync-session",receivedDataHandler: receiveData(data:from:), peerJoinedHandler: peerJoined(_:), peerLeftHandler: peerLeft(_:), peerDiscoveredHandler: peerDiscovered(_:))
             
            let planeMesh = MeshResource.generatePlane(width: 0.15, depth: 0.15)
            let planeMaterial = SimpleMaterial(color:.white,isMetallic: false)
            planeEntity = ModelEntity(mesh: planeMesh, materials: [planeMaterial])
            let planeAnchor = AnchorEntity(plane: .horizontal)
            planeAnchor.synchronization = nil
            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.addChild(planeEntity!)
                arView?.scene.addAnchor(planeAnchor)
                
                arView?.scene.synchronizationService = multipeerSession?.syncService
            } catch let error {
                print("加载文件失败:\(error)")
            }
        }
        func randomColor() -> UIColor{
            return UIColor(red:CGFloat(arc4random()%256)/255.0,green:CGFloat(arc4random()%256)/255.0,blue: CGFloat(arc4random()%256)/255.0,alpha: 1.0 )
        }
        func addGesture(){
            let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
            arView?.addGestureRecognizer(tap)
        }
        @objc func handleTap(_ sender: UITapGestureRecognizer? = nil) {
            guard let raycastResult = raycastResult else {
                print("还未检测到平面")
                return
            }
            
            let box = ModelEntity(mesh: MeshResource.generateBox(size: 0.1), materials: [SimpleMaterial.init(color: randomColor(), isMetallic: false)])
            box.position = [0,0.05,0]
            let anchorEntity = AnchorEntity(raycastResult: raycastResult)
            
            anchorEntity.addChild(box)
            arView?.scene.addAnchor(anchorEntity)
        }
        
        //ARSessionDelegate
        func session(_ session: ARSession, didUpdate frame: ARFrame) {
            guard let arView = arView, let result = arView.raycast(from: arView.center, allowing: .estimatedPlane, alignment: .horizontal).first else{
                return
            }
            raycastResult = result
            planeEntity?.setTransformMatrix(result.worldTransform, relativeTo: nil)
        }
        
        func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
           
        }
        
        
        func receiveData(data:Data,from peer: MCPeerID){
            
            
        }
        
        
        func peerDiscovered(_ peer: MCPeerID) -> Bool {
            guard let multipeerSession = multipeerSession else {
                return false
            }
            
            if multipeerSession.connectedPeers.count > 3 {
                return false
            }else{
                return true
            }
            
        }
        
        func peerJoined(_ peer: MCPeerID) {
        }
        func peerLeft(_ peer: MCPeerID) {
        }
        
        
    
    }
}

#Preview {
    SyncARSession()
}

     代码比上一节代码中的代码要清爽很多,一方面是RealityKit 的同步服务机制简化了人工干预,代码详细功能如下:

   (1) 初始化 Multipeer Session 类,设置 RealityKit 的同步服务。

   (2)进行平面检测,在检测到可用平面时实例化一个指示图标用于指示放置位置。

   (3)添加屏幕单击手势,在平面可用时单击屏幕会在指示图标位置放置一个颜色随机的立方体。

     在第(1)项功能中,即 createPlane()方法中的代码,除了在检测到平面时创建一个指示图标,还初始化了

MultipeerSession 类,传入的是一个用于区分网络服务的 serviceName,然后使用语句 self.scene. synchronizationService = multipeerSession?.syncService设置 RealityKit 的同步服务功能。

     在第(3)项功能中,即 handTap()方法中代码,我们直接在指示图标位置生成了一个颜色随机的立方体。

     需要注意的是,这里并没有生成 ARAnchor 对象,因为在 RealityKit 中,所有的 AnchorEntity 类都会自动进行同步。

     在两台设备 A和B上同时运行本案例(确保两台设备连接到同一个WiFi网络或者都打开蓝牙),在A设备检测到的平面上单击添加立方体,在A、B 连接顺畅的情况下可以看到B设备也会同步出现该立方体,并且立方体所在物理世界中的位置与A设备中的一致,同理,在B设备检测到的平面上单击添加立方体,A设备也会同步出现该立方体,并且立方体所在物理世界中的位置与B设备中的一致,效果如图所示。

使用 RealityKit 同步服务注意事项

    Reality Kit 同步服务让 AR体验实时共享变得前所未有地方便,虚拟元素可以实时地共享到所有参与方,而这主要归功于 Synchronization组件,该组件的主要功能就是通过网络在不同设备间实时同步实体对象,其主要属性有俩个。identifierisOwner:每一个实体对象在网络中的唯一标识符布尔值,用于标识本设备是否拥有该实体对象的所有权。ownershipTransferMode:所有权转移类型,为 SynchronizationComponent. OwnershipTransferMode 枚举值,该枚举共有两个值:autoAccept 为自动授受所有权转移;manual需要使用者进行所有权转移授权。

    可以看出,Synchronization 组件可以对实体对象进行非常严格的所有权控制,防止不经授权对其他设备生成的实体对象进行操作。所有权是 Synchronization 组件中重要的概念,每一个创建实体对象的ARSession 拥有对该实体对象的所有权,只有实体对象的所有者才有权修改该实体对象(如修改尺寸、修改材质、旋转、移动等),修改结果尔后会同步到所有参与者。非实体对象所有者可以修改其本机场景中的实体对象,但无法同步到其他参与者,如果需要同步修改结果,可以向实体对象所有者申请授权,得到授权后就可以成为该实体对象的所有者,修改结果可以同步到所有的参与者,如图8–16所示。

     RealityKit 这么处理的原因是为了防止未授权用户擅自修改其他参与方场景中的虚拟元素,影响其他人的使用体验,保证共享场景中的虚拟元素放置都符合预期。在图8-16中,假设用户①与用户②已经通过 RealityKit 的同步服务进行了同步,①号立方体由用户①创建,②号立方体由用户②创建,这时用户①与用户②都可以看到这两个立方体,此时用户①可以对①号立方体进行任何修改,修改结果会实时地同步到用户②,用户①也可以对②号立方体进行修改,但修改结果并不会被同步。

   

     如果用户①希望能修改②号立方体并且同步到用户②,那么用户①可以申请所有权,在用户②同意授权后,用户①对②号立方体所作的修改就能够同步到用户②。需要注意的是,这时②号立方体的所有权已经转移到用户①,如果用户②要对②号立方体进行修改操作,用户②也需要向用户①申请授权,即一个实体对象的所有者在同一时刻只有一个。进行实体对象操控授权的典型代码如下所示。

public extension HasSynchronization {
    
    func EntityManipulation() {
        if isOwner {
            //拥有某个实体的所有权,可以进行处理
        } else {
            requestOwnership { failure in
                if failure == .granted {
                    //没有某个实体的所有权,进行所有权申请,得到授权后可以进行处理
                }
            }
            
        }
    }
    
  /// Execute the escaping completion if you are the entity owner, once you receive ownership
  /// or call result failure if ownership cannot be granted to the caller.
  /// - Parameter completion: completion of type Result, success once ownership granted, failure if not granted
  func runWithOwnership(
    completion: @escaping (Result<HasSynchronization, Error>) -> Void
  ) {
    if self.isOwner {
      // If caller is already the owner
      completion(.success(self))
    } else {
      self.requestOwnership { (result) in
        if result == .granted {
          completion(.success(self))
        } else {
          completion(
            .failure(result == .timedOut ?
              MHelperErrors.timedOut :
              MHelperErrors.failure
            )
          )
        }
      }
    }
  }
    
    
}

    在代码中,对实体对象进行操作时,首先检查是否拥有该实体对象的所有权,如果有则进行操作,如果没有则向实体对象所有者申请授权,如果授权申请通过则可以进行相应操作。

     实体对象所有者可以设置实体授权模式,RealityKit 支持两种实体授权模式,其值由SynchronizationComponent. OwnershipTransferMode 枚举定义,该枚举共有两个枚举值:autoAccept(自动授权)和 manual(手动授权)。默认授权模式为 autoAccept,即实体对象所有权会自动授权给任何参与者对该实体的所有权申请。设置为 manual 时,当有参与者申请所有权时会触发 SynchronizationEvents.OwnershipRequest 事件,我们需要在该事件的accept()回调方法中对授权进行自定义处理,事件处理可参阅第2章相关内容。

     在一些场合下,我们可能不想某些实体对象或者某些操作被共享,这时候可以将该实体对象的同步组件设置为nil,设置为 nil 的实体对象及其子对象将不会被共享。

具体代码地址:GitHub - duzhaoquan/ARkitDemo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1479410.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RISC-V特权架构 - CSR寄存器

RV32/64 特权架构 - CSR寄存器 1 CSR地址空间2 CSR定义2.1 用户级2.2 监管级2.3 超级监管级2.4 机器级 3 CSR访问3.1 CSRRW3.2 CSRRS3.3 CSRRC3.4 CSRRWI3.5 CSRRSI3.6 CSRRCI 本文属于《 RISC-V指令集基础系列教程》之一&#xff0c;欢迎查看其它文章。 1 CSR地址空间 RISC&…

从 Flask 切到 FastAPI 后,起飞了!

我这几天上手体验 FastAPI&#xff0c;感受到这个框架易用和方便。之前也使用过 Python 中的 Django 和 Flask 作为项目的框架。Django 说实话上手也方便&#xff0c;但是学习起来有点重量级框架的感觉&#xff0c;FastAPI 带给我的直观体验还是很轻便的&#xff0c;本文就会着…

基于Python的电商评论数据采集与分析|电商API接口数据采集

引言 在电商竞争日益激烈的情况下&#xff0c;商家既要提高产品质量&#xff0c;又要洞悉客户的想法和需求&#xff0c;关注客户购买商品后的评论&#xff0c;而第三方商家获取商品评价主要依赖于人工收集&#xff0c;不但效率低&#xff0c;而且准确度得不到保障。通过使用Py…

经典DP-最大子数组

连续子数组的最大和 代码 //定义一个名为"连续子数组的最大和"的公共类 public class 连续子数组的最大和 { // 定义一个静态变量maxn&#xff0c;并赋值为100010 static int maxn100010; // 定义一个静态的整数数组dp&#xff0c;大小为maxn static int[] dp…

Java毕业设计-基于springboot开发的Web社区医院管理服务系统-毕业论文+答辩PPT(有源代码)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1.开发说明2.需求分析3、系统功能结构 三、系统实现展示1、系统功能模块2、管理员功能模块3、用户功能模块4、医生功能模块 四、毕设内容和源代码获取总结 Java毕业设计-基于springboot开发…

官网万词霸屏推广+关键词排名优化源码系统 带完整的安装代码包以及搭建教程

随着搜索引擎算法的不断更新和市场竞争的加剧&#xff0c;传统的SEO方法已经难以满足企业对于快速、高效推广的需求。罗峰结合多年的互联网营销经验和最新的搜索引擎优化技术&#xff0c;给大家推荐一款集网站搭建、关键词优化、数据分析于一体的源码系统。 以下是部分代码示例…

如何使用Spring Boot轻松实现国际化和本地化

文章目录 什么是国际化SpringBoot 国际化实践出真知新建Properties文件修改配置文件测试获取所有国际化资源 总结 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 什么是国际化 国际化&…

opencv--使用直方图找谷底进行确定分割阈值

直方图原理就不说了&#xff0c;大家自行百度 直方图可以帮助分析图像中的灰度变化&#xff0c;进而帮助确定最优二值化的灰度阈值&#xff08;threshold level&#xff09;。如果物体与背景的灰度值对比明显&#xff0c;此时灰度直方图就会包含双峰&#xff08;bimodal histo…

Java核心API-多线程

多线程 文章目录 多线程前言一、多线程1、多线程的概念2、多线程的好处 二、主线程1、Thread类2、主线程 三、线程的创建和启动1、创建线程的两种方式2、使用线程的步骤 四、继承Thread类创建线程五、实现Runnable接口创建线程六、比较两种创建线程的方式1、继承Thread类2、实现…

Python爬取网站视频资源

思路&#xff1a; 在界面找到视频对应的html元素位置&#xff0c;观察发现视频的url为https://www.pearvideo.com/video_视频的id&#xff0c;而这个id在html中的href中&#xff0c;所以第一步需要通过xpath捕获到所需要的id 在https://www.pearvideo.com/video_id的页面&…

C语言while 语句的基本格式是什么?

一、问题 C语⾔中有三种循环语句&#xff0c;while 语句是其中的⼀个&#xff0c;它的基本格式是怎样的呢&#xff1f; 二、解答 while 语句的⼀般形式为&#xff1a; while(表达式) 语句; 其中&#xff0c;表达式是循环条件&#xff0c;语句为循环体。 注意&#xff1a; …

测试环境搭建整套大数据系统(七:集群搭建kafka(2.13)+flink(1.13.6)+dinky(0.6)+iceberg)

一&#xff1a;搭建kafka。 1. 三台机器执行以下命令。 cd /opt wget wget https://dlcdn.apache.org/kafka/3.6.1/kafka_2.13-3.6.1.tgz tar zxvf kafka_2.13-3.6.1.tgz cd kafka_2.13-3.6.1/config vim server.properties修改以下俩内容 1.三台机器分别给予各自的broker_id…

奇点云:SAFe框架下,我们对平台软件工程生产线做了4项改造

导读&#xff1a; 客户规模扩大&#xff0c;如何保证大数据软件产品和服务质量始终如一&#xff1f;几乎所有成长中的软件厂商&#xff0c;尤其是需要通过私有化部署交付的厂商&#xff0c;都会面临这个问题。正如《人月神话》中多次表明的&#xff0c;单纯地增加人手、扩大团队…

npm使用国内淘宝镜像的方法整理

命令配置安装&#xff1a; 淘宝镜像&#xff1a; npm config set registry https://registry.npm.taobao.org/ 官方镜像&#xff1a; npm config set registry https://registry.npmjs.org 通过cnpm安装&#xff1a; npm install -g cnpm --registryhttps://registry.npm.…

Java-常用集合

Jva常用集合 一、Java 集合框架体系二、Collection接口和方法1. List接口List 接口主要实现类&#xff1a;ArrayListList 的实现类之二&#xff1a;LinkedListList 的实现类之三&#xff1a;Vector 2. Set接口Set 主要实现类&#xff1a;HashSetSet 实现类之二&#xff1a;Link…

SpringBoot 手写 Starter

spring-boot-starter 模块 1.介绍 SpringBoot中的starter是一种非常重要的机制&#xff0c;能够抛弃以前繁杂的配置&#xff0c;将其统一集成进starter&#xff0c;应用者只需要在maven中引入starter依赖&#xff0c;SpringBoot就能自动扫描到要加载的信息并启动相应的默认配…

WordPress分类目录ID怎么看?如何查找WordPress标签ID?

在WordPress网站中&#xff0c;我们需要判断某篇文章是否属于某个分类目录&#xff0c;或者是否拥有某个标签&#xff0c;那么就需要用到分类目录ID和标签ID&#xff0c;那么WordPress分类目录ID怎么看&#xff1f;如何查找WordPress标签ID&#xff1f;下面boke112百科就跟大家…

MySQL 自增列解析(Auto_increment)

MySQL数据库为列提供了一种自增属性&#xff0c;当列被定义为自增时。Insert语句对该列即使不提供值&#xff0c;MySQL也会自动为该列生成递增的唯一标识&#xff0c;因此这个特性广泛用于主键的自动生成。 一、自增列的用法 自增列具有自动生成序列值&#xff0c;整型&#…

Linux系统编程入门(下)

Linux系统编程 第一章 Linux系统编程入门&#xff08;下&#xff09;1.6 GDB 调试1.7 标准C库IO函数和Linux系统IO函数对比 第一章 Linux系统编程入门&#xff08;上&#xff09; 第一章 Linux系统编程入门&#xff08;下&#xff09; 1.6 GDB 调试 &#xff08;1&#xff0…

AOP(黑马学习笔记)

AOP基础 学习完spring的事务管理之后&#xff0c;接下来我们进入到AOP的学习。 AOP也是spring框架的第二大核心&#xff0c;我们先来学习AOP的基础。 在AOP基础这个阶段&#xff0c;我们首先介绍一下什么是AOP&#xff0c;再通过一个快速入门程序&#xff0c;让大家快速体验A…