【iOS ARKit】RealityKit 同步机制
协作 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 MultipeerConnectivitystruct SyncARSession: View {static var arView: ARView!static var multipeerSession: MultipeerSession?var body: some View {SnycARSessionContent().onDisappear {SyncARSession.arView.session.delegate = nilSyncARSession.arView.session.pause()SyncARSession.arView = nilSyncARSession.multipeerSession?.endConnect()SyncARSession.multipeerSession = nilprint("SyncARSession onDisappear")}.edgesIgnoringSafeArea(.all).navigationTitle("ARSession同步")}}struct SnycARSessionContent: UIViewRepresentable {func makeUIView(context: Context) -> some ARView {let arView = ARView(frame: .zero)arView.automaticallyConfigureSession = falselet config = ARWorldTrackingConfiguration()config.planeDetection = .horizontalconfig.isCollaborationEnabled = truearView.session.run(config,options: [.resetTracking,.removeExistingAnchors])arView.session.delegate = context.coordinatorSyncARSession.arView = arViewcontext.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? = nilvar 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 = nildo {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)}//ARSessionDelegatefunc 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 = resultplaneEntity?.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 inif 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 grantedfunc runWithOwnership(completion: @escaping (Result<HasSynchronization, Error>) -> Void) {if self.isOwner {// If caller is already the ownercompletion(.success(self))} else {self.requestOwnership { (result) inif result == .granted {completion(.success(self))} else {completion(.failure(result == .timedOut ?MHelperErrors.timedOut :MHelperErrors.failure))}}}}}
在代码中,对实体对象进行操作时,首先检查是否拥有该实体对象的所有权,如果有则进行操作,如果没有则向实体对象所有者申请授权,如果授权申请通过则可以进行相应操作。
实体对象所有者可以设置实体授权模式,RealityKit 支持两种实体授权模式,其值由SynchronizationComponent. OwnershipTransferMode 枚举定义,该枚举共有两个枚举值:autoAccept(自动授权)和 manual(手动授权)。默认授权模式为 autoAccept,即实体对象所有权会自动授权给任何参与者对该实体的所有权申请。设置为 manual 时,当有参与者申请所有权时会触发 SynchronizationEvents.OwnershipRequest 事件,我们需要在该事件的accept()回调方法中对授权进行自定义处理,代码实例如下。
//收到实体权限变更请求let subscribe = arView.scene.subscribe(to: SynchronizationEvents.OwnershipRequest.self, { event inif (Int(event.entity.name) ?? 0) % 2 == 0 {//不允许变更权限print("------------不允许变更权限")return}else{//接受变更权限print("------------允许变更权限")event.accept()}})subscribes.append(subscribe)//监听添加的实体Entitylet sub = arView.scene.subscribe(to: SceneEvents.DidAddEntity.self, {[weak self] event inself?.didAddEntitiy(entity: event.entity)})subscribes.append(sub)func didAddEntitiy(entity: Entity) {if let entity = entity as? HasCollision {self.arView?.installGestures(.all, for: entity).forEach({ entityGestureRecognizer inentityGestureRecognizer.addTarget(self, action: #selector(handleModelGesture))})}}@objc func handleModelGesture(_ sender: Any) {if let ges = sender as? EntityGestureRecognizer {//获取权限ges.entity?.runWithOwnership(completion: { res inif case .success(_) = res {}else {print("--------获取所有者权限失败")}})}}
public extension HasSynchronization {/// 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 grantedfunc runWithOwnership(completion: @escaping (Result<HasSynchronization, Error>) -> Void) {if self.isOwner {// If caller is already the ownercompletion(.success(self))} else {self.requestOwnership { (result) inif result == .granted {completion(.success(self))} else {completion(.failure(result == .timedOut ?MHelperErrors.timedOut :MHelperErrors.failure))}}}}}
在一些场合下,我们可能不想某些实体对象或者某些操作被共享,这时候可以将该实体对象的同步组件设置为nil,设置为 nil 的实体对象及其子对象将不会被共享。
具体代码地址:GitHub - duzhaoquan/ARkitDemo
相关文章:

【iOS ARKit】RealityKit 同步机制
协作 Session 可以很方便地实现多用户之间的AR体验实时共享,但开发者需要自行负责并确保AR场景的完整性,自行负责虚拟物体的创建与销毁。为简化同步操作,RealityKit 内建了同步机制,RealityKit 同步机制基于 Multipeer Connectivi…...

【数据结构与算法】整数二分
问题描述 对一个排好序的数组,要求找到大于等于7的最小位置和小于等于7的最大位置 大于等于7的最小位置 易知从某个点开始到最右边的边界都满足条件,我们要找到这个区域的最左边的点。 开始二分! left指针指向最左边界,right…...

java项目打包运行报异常:xxxxx-1.0-SNAPSHOT.jar中没有主清单属性
pom.xml中加入这段话即可 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.4.4</version><executions><execution><…...
MAC-键盘command快捷键、设置windows快捷键
在 Windows PC 专用键盘上,请用 Alt 键代替 Option 键,用 Ctrl 键或 Windows 标志键代替 Command 键。 Mac 键盘快捷键 - 官方 Apple 支持 (中国) 设置windows快捷键 使用mac外接适用于windows的键盘时,如何设置快捷键?_mac外…...

C++ 补充之常用遍历算法
C遍历算法和原理 C标准库提供了丰富的遍历算法,涵盖了各种不同的功能。以下是一些常见的C遍历算法以及它们的概念和原理的简要讲解: for_each:对容器中的每个元素应用指定的函数。 概念:对于给定的容器和一个可调用对象ÿ…...

【Linux杂货铺】调试工具gdb的使用
目录 🌈前言🌈 📁背景介绍 📁 使用 list [行号] / [函数名] run/r break/b [行号] / [函数名] info break disable break enable break delete break [断点编号] next/n step/s continue/c finish print/p [变量…...

FL Studio Producer Edition2024中文进阶版Win/Mac
FL Studio Producer Edition,特别是其【中文进阶版 Win/Mac】,是数字音乐制作领域中的一款知名软件。它为广大音乐制作人、声音工程师以及音乐爱好者提供了一个从音乐构思到最终作品发布的完整解决方案。这个版本特别为中文用户优化,并兼容W…...

无需邀请码,Xinstall实现精准分享归因
在如今的移动互联网时代,分享已经成为了我们日常生活中不可或缺的一部分。无论是社交媒体上的好友分享,还是应用内的内容分享,分享都能够帮助我们快速传播信息,扩大影响力。然而,对于开发者而言,分享却带来…...

机器人与AGI会撞出什么火花?
真正的科技变革是不是就要来临了?各方大佬都开始布局机器人,对于普通人的就业会造成什么影响? 优牛企讯-企业动态信息监控专家 在优牛企讯-企业动态监控专家搜索可知,全国目前的机器人公司已经达到了26401家,近一年…...

Linux yum安装pgsql出现Bad GPG signature错误
官方文档:https://www.postgresql.org/download/linux/redhat/ sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm sudo yum install -y postgresql12-server sudo /usr/pgsql-12/bin/…...

第18章-DHCP
1. 产生背景 2. 概述 2.1 定义 2.2 特点 2.3 DHCP系统组成 3. DHCP工作原理 3.1 前提条件 3.2 场景 3.3 分配IP地址工作机制 3.4 特殊情况处理 3.5 IP地址租约更新 4. DHCP中继代理 4.1 现实场景 4.2 工作机制 1. 产生背景 现实问题: 小型网络中&…...

[物联网] OneNet 多协议TCP透传
[物联网] OneNet 多协议TCP透传 STM32物联网–ONENET云平台的多协议接入产品创建 : https://blog.csdn.net/qq_44942724/article/details/134492924 Onenet tcp 透传 : https://blog.csdn.net/flyme2010/article/details/107086001 tcp服务端测试工具 : http://tcp.xnkiot.com/…...

如何让网页APP化 渐进式Web应用(PWA)
前言 大家上网应该发现有的网页说可以安装对应应用,结果这个应用好像就是个web,不像是应用,因为这里采用了PWA相关技术。 PWA,全称为渐进式Web应用(Progressive Web Apps),是一种可以提供类似…...

50 vmalloc 的实现
前言 这里说的是 内核中分配按页分配的场景 常用于 驱动什么的, 分配 中大型空间 由于 连续的 n 个页是分别使用 alloc_pages 分配的, 因此是 虚拟地址空间连续, 但是 物理地址空间不连续 如何分配对象 两个步骤, __get_vm_area_node 获取为 size 分配的 vma 区间, 然后…...

程序员的金三银四求职宝典!
目录 编辑 程序员的金三银四求职宝典 一、为什么金三银四是程序员求职的黄金时期? 二、如何准备金三银四求职? 1. 完善简历 2. 增强技术能力 3. 提前考虑目标公司 4. 提前准备面试 三、程序员求职的常见面试题 1. 数据结构和算法 2. 数据库 …...

day04_拦截器Apifox角色管理(登录校验,API接口文档,权限管理说明,角色管理,添加角色,修改角色,删除角色)
文章目录 1. 登录校验1.1 需求说明1.2 实现思路1.3 ThreadLocal1.4 AuthContextUtil1.5 拦截器使用1.5.1 拦截器开发1.5.2 拦截器注册 1.6 代码优化1.6.1 配置优化1.6.2 代码优化1.6.3 前端修改 2. API接口文档2.1 Apifox接口管理平台2.1.1 接口管理平台简介2.1.2 Apifox简介2.…...

在线上传解压PHP文件代码,压缩/压缩(网站一键打包)支持密码登录
在线上传解压PHP文件代码,压缩/压缩(网站一键打包)支持密码登录 资源宝分享:www.httple.net 如果你没有主机控制面板这个是最好选择,不需要数据库,上传当控制面板使用,无需安装任何扩展,安全高,…...
【刷题】模拟
模拟算法:题目中已经告诉应该怎么做了,只需要模拟即可,思路比较简单,比较考察代码能力。 一般先在草稿纸上模拟流程,如果直接写代码,容易忽视细节,并且不容器调试! 优化策略&#…...

【打工日常】使用docker部署在线Photopea用于linux下替代ps
一、Photopea介绍 linux没有ps适配,对于有时候工作来说确实不方便,我找了很久,才找到了一款功能可以跟ps接近的在线软件,使用docker部署就可以了。它是ps的最佳替代品之一,其界面几乎与ps相同,只不过它是在…...

leetcode 热题 100_盛最多水的容器
题解一: 双指针遍历:容量计算公式为min(左高度,右高度)*底部距离,我们可以令底部距离逐步递减(左右两边的指针向中部移动)。此时对于min(左高度,右高度),假设较高的线向中部移动&…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...