大多数浏览器和
Developer App 均支持流媒体播放。
-
了解用于空间计算的 ARKit
了解如何使用 ARKit 的跟踪和场景理解功能来开发全新的沉浸式 App 和游戏体验。了解 VisionOS 和 ARKit 如何协同工作,在帮助你创建理解周围环境的 App 的同时,保护你的隐私。探索 ARKit API 的最新更新,并跟随我们的演示了解如何在 App 中利用手势跟踪和场景几何。
资源
相关视频
WWDC23
-
下载
♪ 悦耳的器乐嘻哈 ♪ ♪ Ryan Taylor: 大家好!我是 Ryan Conner Brooks:我是 Conner Ryan: 在本次讲座中 我们将为你介绍 用于空间计算的 ARKit 我们将讨论 它在这个新平台上的关键作用 以及你如何利用它 来构建下一代 App 的方法 ARKit 使用复杂的计算机视觉算法 来构建对你周围的世界 以及对你的动作的理解 我们在 iOS 11 中 首次引入了这项技术 它是开发者创建 于两手之间即可体验的 惊人的增强现实内容的方法 在这个平台上 ARKit 已经成为了 全面的系统服务 它是用新的实时基础 从头开始重建的 ARKit 已经深深地融入了 整个操作系统的结构中 为从窗口互动到 沉浸式玩游戏等各方面提供支持 作为此次旅程的一部分 我们还对我们的 API 进行了全面改造 该新设计是我们在 iOS 上 所有学习成果 和空间计算的 独特需求的结合 我们认为你一定会喜欢它的 ARKit 提供了许多强大的功能 你可以结合这些功能 来做一些不可思议的事情 如在桌子上放置虚拟内容 你可以伸手触摸该内容 就像它真的在那里一样 然后 看这些内容与现实世界互动 这真是非常神奇的体验 现在 你已经瞥见了在这个新平台上 使用 ARKit 可以实现的内容 让我带你了解一下我们的日程表 首先是概述构成 API 的 基本概念和构建基块 接下来 我们将深入研究世界跟踪 这对将虚拟内容放置在现实世界中 是至关重要的 然后 我们将探索 我们的场景理解功能 它会提供你周围环境的有用信息 之后 我们将为你 介绍我们最新的功能 手势跟踪 这是一个令人兴奋的新功能 你可以利用它来放置 与手势相关联的虚拟内容 或构建其他类型的定制互动 最后 我们回到原点 通过检查我们刚才 向你展示的视频中的代码 来看看这些功能的实际应用 好的 我们开始吧! 我们的新 API 以两种令人振奋的方式 精心打造 时髦的 Swift 和经典的 C 语言 现在的 ARKit 功能均可自选 我们希望开发者 拥有尽可能多的灵活性 这样你就可以 简单地挑选你需要的东西 来构建你的体验 对 ARKit 数据的访问 是以隐私至上的方式设计的 我们采取了保护措施 来保护人们的隐私 同时也为开发者保持了简洁性 API 由三个基本构建基块组成: Session、 DataProvider 和 Anchor 让我们从 Anchor 开始 然后回到 Session 一个 Anchor 代表了现实世界中的 一个位置和方向 所有的 Anchor 都包含一个唯一的标识符 以及一个 transform 有些类型的 Anchor 是可跟踪的 当一个可跟踪的 Anchor 没有被跟踪时 你应该隐藏 你用它定位的任何虚拟内容 一个 DataProvider 代表一个单独的 ARKit 功能 DataProvider 让你可以轮询 或观察数据更新 例如 Anchor 的变化 不同类型的 DataProvider 提供不同的数据类型 一个 Session 代表了 一套组合的 ARKit 功能 你可以利用这套组合功能 来构建特定的体验 你可以通过向 Session 提供一组 DataProvider 来运行它 Session 开始运行后 DataProvider 会开始接收数据 根据不同类型的数据 更新会以不同的频率 异步到达 让我们继续谈一谈隐私 以及你的 App 如何获取 ARKit 数据 隐私是一项基本人权 它也是我们的核心价值之一 ARKit 的架构和 API 经过了深思熟虑的设计 以保护人们的隐私 为了让 ARKit 构建对周围世界的理解 该设备有许多摄像头 和其他类型的传感器 来自这些传感器的数据 如相机画面 绝不会被发送到客户端空间 相反 传感器数据 会被发送到 ARKit 守护进程 由我们的算法进行安全处理 由这些算法产生的数据 在被转发给任何请求数据的客户端 如你的 App 之前 会经过认真地评判 访问 ARKit 数据有几个先决条件 首先 你的 App 必须进入 Full Space ARKit 不会向处于 共享空间的 App 发送数据 其次 某些类型的 ARKit 数据 需要获得权限才能访问 如果对方没有授权 那么我们就不会 向你的 App 发送该类型的数据 为了方便这一点 ARKit 提供了一个 方便的授权 API 来处理权限 使用 Session 你可以 为你想访问的数据类型请求授权 如果你不这么做 当你运行 Session 时 如果有必要 ARKit 会自动提示对方 授予权限 这里 我们正在请求 访问手势跟踪数据 你可以在一个请求中 把你需要的所有授权类型集中起来 一旦我们有了授权结果 我们就会对它们进行迭代 并检查 每个授权类型的状态 如果对方已经授权 状态会是 allowed 试图使用对方拒绝授权访问的 DataProvider 运行一个 Session 将会导致 Session 失败 接下来 让我们仔细看看 ARKit 在这个平台上支持的各个功能 首先来看看世界跟踪 世界跟踪可以将虚拟内容 定位在现实世界中 ARKit 会跟踪设备 在六个自由度上的运动 并更新每个 Anchor 这样虚拟内容就能保持 与环境的相对位置不变 世界跟踪使用的 DataProvider 类型 叫作 WorldTrackingProvider 它会提供几个重要功能 它可以让你添加 WorldAnchor 之后 ARKit 会对其进行更新 以便其在设备移动时 与环境的相对位置保持不变 WorldAnchors 是放置虚拟内容的 重要工具 你添加的所有 WorldAnchor 都会在 App 启动 和重启时自动保留 如果这一行为 不适合你所构建的体验 你可以在使用完 Anchor 后 直接将其删除 这样它们就不会保留了 值得注意的是 在某些情况下 保留位置是不可用的 你也可以使用 WorldTrackingProvider 来获取 相对于 App 原点的设备位姿 如果你想使用 Metal 技术自行渲染 这一点非常重要 首先 让我们仔细看看 什么是 WorldAnchor 以及为什么你要使用它 WorldAnchor 是 一个带有使用 transform 的 初始化器的 TrackableAnchor 即你要放置的 Anchor 与 App 原点的 相对位置和方向 我们准备了一个例子 来帮助你直观地了解 未被定位的虚拟内容 与被定位的内容之间的区别 这里有两个立方体 左边的蓝色立方体 没有被 WorldAnchor 更新 而右边的红色立方体 正在被 WorldAnchor 更新 这两个立方体都是在 App 启动时 相对于 App 原点放置的 随着设备的移动 两个立方体都保持在 它们被放置的位置 你可以按住数码表冠 来重新定位 App 在重新定位时 App 原点会移动到 你的当前位置 请注意 没有被定位的蓝色立方体 会重新定位 以保持它与 App 原点的 相对位置; 而被定位的红色立方体 则保持与现实世界的相对位置不变 让我们来看看 WorldAnchor 的 保留位置是如何工作的 随着设备的移动 ARKit 会构建 你周围环境的地图 当你添加 WorldAnchor 时 我们会将它们插入到地图中 并自动为你保留它们 只有 WorldAnchor 的标识符 和 transform 会被保留下来 其他数据都不会被保留 如虚拟内容 是否保留 WorldAnchor 标识符 和与它们关联的虚拟内容的映射关系 都取决于你 地图是基于位置的服务 所以当你把你的设备 带到一个新地方 例如从家中带到办公室 你家的地图将被卸载 然后一个不同的地图 会定位到你的办公室 你在这个新地址添加的 Anchor 都会添加到该地图中 当你下班离开办公室 回家时 ARKit 在你的办公室 构建的地图 以及你在那里放置的 Anchor 都会被卸载 不过 我们再一次 自动保留了地图和你的 Anchor 回到家后 ARKit 会意识到位置已经改变 我们将通过检查这个位置的现有地图 以开始重新定位 如果我们找到了地图 我们将用它进行定位 我们还会重新跟踪到 你之前在家里添加的所有 Anchor 让我们继续讨论设备的位姿 除了添加和删除 WorldAnchor 外 你还可以使用 WorldTrackingProvider 来获取设备的位姿 位姿是设备相对于 App 原点的 位置和方向 如果你在完全沉浸体验中 使用 Metal 技术和 CompositorServices 进行渲染 那你就需要查询位姿 该查询代价较高 在为其他类型的 App logic 查询设备位姿时 如内容放置 要小心谨慎 让我们快速浏览 一个简化的渲染示例 以了解如何从 ARKit 向 CompositorServices 提供设备位姿 我们有一个 Renderer 结构 它会保存我们的 Session、 WorldTrackingProvider 和最新的位姿 在初始化 Renderer 时 我们首先要创建一个 Session 接下来 创建一个 WorldTrackingProvider 我们会在渲染每一帧时 用它来查询设备的位姿 现在 我们可以使用 我们需要的任何 DataProvider 继续运行我们的 Session 在这个例子中 我们只需要 使用 WorldTrackingProvider 我们还创建了一个位姿 以避免渲染函数中的分配 现在 让我们跳到渲染函数 我们会以帧速率调用它 使用 CompositorServices 的 可绘制对象 提取 target render time 接下来 使用 target render time 来查询设备的位姿 如果成功的话 我们可以提取一个 相对于 App 原点的位姿变换 这就是用于 渲染内容的 transform 最后 在我们提交帧进行合成之前 我们要在可绘制对象上设置位姿 这样合成器就会知道 我们用什么位姿来渲染该帧的内容 想了解关于自己渲染的更多信息 请看专注于使用 Metal 技术 创建沉浸式 App 的讲座 另外 还有一个 关于空间计算性能考虑的精彩讲座 我们推荐你也去看看 接下来 我们来看一下场景理解 场景理解是一类以不同方式 告知你周围环境的功能 让我们从平面检测开始介绍 平面检测为 ARKit 在现实世界中 检测到的水平 和垂直表面提供 Anchor 平面检测使用的 DataProvider 类型 叫作 PlaneDetectionProvider 在你的周围环境中检测到的平面 会以 PlaneAnchor 的形式提供给你 PlaneAnchor 可以用来简化放置内容 例如将一个虚拟物体放在桌子上 另外 你可以 使用平面来进行物理模拟 利用基本的 平面几何形状 如地板或墙面 就足够了 每个 PlaneAnchor 都包括对齐方式 即水平或垂直 平面的几何结构 和语义分类 平面可以被分类为不同类型的表面 例如地板或桌子 如果我们无法确定某个表面 其提供的分类会根据情况 被标记为未知、未确定或不可用 现在 我们来继续讨论场景几何 场景几何提供了 包含多边形网格的 Anchor 该网格会估计现实世界的形状 场景几何使用的 DataProvider 类型 叫作 SceneReconstructionProvider 当 ARKit 扫描你周围的世界时 我们会将你的周围环境 重建为一个细分网格 然后以 MeshAnchor 的形式 提供给你 和 PlaneAnchor 一样 MeshAnchor 也可以用来 简化放置内容 如果你需要虚拟内容 与简单的平面物体 之外的物体进行交互 你也可以实现 更高保真度的物理模拟 每个 MeshAnchor 都包含了网格的几何形状 该几何形状包含了顶点、法线、面 和语义分类 每个面都如此 网格面可以被分类为 各种不同类型的物体 如果我们无法识别一个物体 提供的分类就会为 none 最后 我们来看一下图像跟踪 图像跟踪让你能够检测 现实世界中的 2D 图像 图像跟踪使用的 DataProvider 类型 叫作 ImageTrackingProvider 你可以使用一组你想检测的 ReferenceImage 来配置 ImageTrackingProvider 这些 ReferenceImage 可以通过几种不同的方式创建 其中一种方式是 在你的项目的资产目录中 从 AR 资源组加载它们 另外 你也可以通过提供 一个 CVPixelBuffer 或 CGImage 来初始化 一个 ReferenceImage 当检测到图像时 ARKit 会向你提供 一个 ImageAnchor ImageAnchor 可以用来在已知的、 静态放置的图像上放置内容 例如 你可以在电影海报旁边 显示一些关于电影的信息 ImageAnchor 是包含一个估计比例因子的 TrackableAnchor 它会表明检测到的图像的尺寸 与你指定的物理尺寸 和 Anchor 所对应的 ReferenceImage 的对比 现在 为了向你介绍 我们的新功能手势跟踪 并带你浏览例子 让我们有请 Conner Conner:大家好 我们来看一下手势跟踪 它是 ARKit 的一个全新的功能 手势跟踪为你提供了 包含你双手的骨骼数据的 Anchor 手势跟踪使用的 DataProvider 类型 叫作 HandTrackingProvider 检测到你的手后 它们会以 HandAnchor 的形式 提供给你 HandAnchor 是 TrackableAnchor HandAnchor 包括骨骼和手性 手性帮助我们辨别左手与右手 HandAnchor 变换 是腕部相对于 App 原点的变换 骨骼由关节组成 可以通过名称查询 关节包括父关节、名称、 与父关节相关联的 localTransform、 相对于根关节的 rootTransform、 以及每个关节包含的一个布尔类型 布尔类型用来表明 该关节是否被跟踪了 我们在这里列举了手部骨骼中 所有的可用关节 让我们来看看 关节层次结构的一个子集 手腕是手的根关节 每根手指的第一个关节 都是手腕的子关节 例如 1 是 0 的子关节 随后的手指关节 都是前一个关节的子关节 例如 2 是 1 的子关节等等 HandAnchor 可以用来放置 与你的手相关联的内容 或者检测自定义手势 接收 HandAnchor 有两种方式 你可以应用轮询进行更新 或者在 Anchor 可用时 异步接收 Anchor 我们将在稍后的 Swift 例子中 了解异步更新 让我们先将手部 Anchor 轮询 添加到我们之前的渲染器中 这是我们更新后的结构定义 我们已经添加了 HandTrackingProvider 和左右手的 Anchor 在我们更新的 init 函数中 我们创建了新的 HandTrackingProvider 并将其添加到了 我们运行的 DataProvider 列表中 然后我们创建了轮询 需要的左右手 Anchor 请注意 我们提前创建了这些 这是为了避免渲染循环中的分配 随着我们的结构更新和初始化完成 我们可以在我们的渲染函数中 调用 get_latest_anchors 我们传递 DataProvider 和预先分配的手部 Anchor 把 Anchor 填入最新的可用数据 最新的 Anchor 填入完成后 我们就可以在我们的 体验中使用它们的数据了 非常棒 现在该重新看一下 我们之前给你展示的例子了 我们使用了 ARKit 和 RealityKit 功能的组合 来构建这个体验 场景几何被用作 物理和手势的碰撞器 而手势跟踪则被用来 与立方体实体直接互动 让我们来看看 这个例子是如何构建的 首先 我们将查看 App 的结构和视图模型 接下来 我们将初始化 ARKit Session 然后 我们将添加指尖碰撞器 和来自场景重建的碰撞器 最后 我们将看看 如何用手势添加立方体 我们直接进入代码部分吧 这是我们的 App TimeForCube 我们使用了相对标准的 SwiftUI App 和场景设置 在我们的场景中 我们创建了一个 ImmersiveSpace 我们需要移动到 Full Space 以便访问 ARKit 数据 因此 IimmersiveSpace 是必需的 在 ImmersiveSpace 中 我们定义了一个 RealityView 它将展示我们的视图模型中的内容 视图模型是我们的 App 大部分逻辑所在的地方 让我们快速浏览一下 视图模型持有 ARKit Session、 我们将使用的 DataProvider、 包含我们创建的所有其他实体的 内容实体 以及我们的场景和手部碰撞器地图 我们的视图模型还提供了 我们将从 App 中 调用的各种功能 我们将在 App 的 上下文中查看这些功能 我们要调用的第一个函数 是在 RealityView 的 make 闭包中 设置 contentEntity 把这个实体添加到 RealityView 的内容中 这样视图模型就可以 把实体添加到视图的内容中了 setupContentEntity 将所有我们地图中的 所有手指实体简单地添加为 contentEntity 的子实体 然后返回它 好极了! 让我们继续讲一下 Session 初始化 Session 初始化会在 三个任务中的一个中运行 第一个任务会调用 runSession 函数 这个函数会使用两个 DataProvider 来简单地运行 Session Session 运行后 我们可以开始接收 Anchor 更新 让我们创建并更新 我们的指尖碰撞器 用它来和立方体互动 这里是我们处理手部更新的任务 它的函数会迭代 DataProvider 上的 Anchor 更新的异步序列 我们要确保跟踪手部的 Anchor 获取食指尖的关节 检查是否也跟踪了该关节 然后计算处理食指指尖 相对于 App 原点的变换 最后 查找应该更新的手指实体 并设置它的变换
让我们再看一下手指实体地图 我们通过 ModelEntity 的 一个扩展创建双手的实体 这一扩展创建了一个 带有碰撞形状的 5 毫米球体 我们添加一个运动物理体组件 然后添加一个 不透明度组件来隐藏这个实体 虽然我们会在用例中 隐藏这些手指实体 但通过把这些手指实体可视化 来验证一切都运行正常是很好的 让我们暂时把不透明度设置为 1 确保实体在正确的位置 很好! 可以看到球体就在我们的指尖! 注意 我们的手遮住了部分球体 这叫作手遮挡 它是一项系统功能 让人们可以 在虚拟内容上看到他们的手 这是默认启用的 如果我们想更清楚地看到球体 我们可以在使用场景中 使用 upperLimbVisibility 设置器 来配置手部遮挡的可见性 如果我们将肢体可见性设置为隐藏 无论我们的手在哪里 我们都能看到整个球体 在我们的例子中 我们保留上肢的可见性 为默认值 并设置不透明度为 0 很好!现在我们来添加场景碰撞器 我们会把场景碰撞器 用于物理和手势目标 这是调用我们模型上的函数的任务 我们迭代 DataProvider 上 Anchor 更新的异步序列 尝试从 MeshAnchor 生成一个 ShapeResource 然后打开 Anchor 更新的事件 如果我们要添加一个 Anchor 我们要创建一个新实体 设置它的 transform 添加一个碰撞和物理体组件 然后添加一个输入目标组件 这样这个碰撞器 就可以成为手势的目标 最后 把一个新的实体 添加到我们的地图中 把它作为我们的内容实体的子实体 要更新一个实体 我们要从地图上检索它 然后更新它的变换 和碰撞组件的形状 要移除一个实体 我们要把相应的实体 从其父实体和地图中移除 现在有了手部和场景碰撞器 我们就可以使用姿势添加立方体了 我们为所有实体添加了 一个 SpatialTapGesture 这样如果有人点击了 RealityView 内容中的 任何实体 我们都会知道 轻点结束后 我们会收到一个 从全局坐标 转换为场景坐标的 3D 位置 让我们把这个位置可视化 如果我们在点击的位置 添加一个球体 我们会看到这样的情况 现在 我们告诉视图模型 添加一个相对于这个位置的立方体 要添加一个立方体 我们首先要计算出一个放置位置 这个位置比点击位置高 20 厘米 然后我们创建立方体 并将它的位置 设置为我们计算出的位置 添加一个 InputTargetComponent 它让我们设置实体 要对什么类型的手势做出反应 在我们的用例中 对这些立方体 我们只允许间接输入类型 因为我们的指尖碰撞器 将提供直接互动 添加一个带有自定义参数的 PhysicsBodyComponent 让物理交互更加漂亮 最后 把立方体添加到内容实体中 这意味着终于可以与立方体互动了 让我们最后再从头到尾 看一下我们的例子 每次点击场景碰撞器或立方体 点击位置上方 就会添加一个新立方体 物理系统会让立方体 落到场景碰撞器上 手部碰撞器则可以 让我们与这些立方体互动 要了解 关于 RealityKit 的更多信息 请查看关于 使用 RealityKit 进行空间计算的 介绍性讲座 如果你已经在 iOS 上 体验了现有的 ARKit 并有兴趣 在这个平台体验 请务必观看 关于这个主题的讲座 以获得进一步的指导 我们整个团队 对你能接触新版本的 ARKit 感到异常兴奋 我们非常期待能看到 你利用这个令人兴奋的新平台 创造的开创性 App Ryan:感谢观看! ♪
-
-
5:20 - Authorisation API
// Privacy // Authorization session = ARKitSession() Task { let authorizationResult = await session.requestAuthorization(for: [.handTracking]) for (authorizationType, authorizationStatus) in authorizationResult { print("Authorization status for \(authorizationType): \(authorizationStatus)") switch authorizationStatus { case .allowed: // All good! break case .denied: // Need to handle this. break ... } } }
-
10:20 - World Tracking Device Pose Render Struct
// World tracking // Device pose #include <ARKit/ARKit.h> #include <CompositorServices/CompositorServices.h> struct Renderer { ar_session_t session; ar_world_tracking_provider_t world_tracking; ar_pose_t pose; ... }; void renderer_init(struct Renderer *renderer) { renderer->session = ar_session_create(); ar_world_tracking_configuration_t config = ar_world_tracking_configuration_create(); renderer->world_tracking = ar_world_tracking_provider_create(config); ar_data_providers_t providers = ar_data_providers_create(); ar_data_providers_add_data_provider(providers, renderer->world_tracking); ar_session_run(renderer->session, providers); renderer->pose = ar_pose_create(); ... }
-
10:21 - World Tracking Device Pose Render function
// World tracking // Device pose void render(struct Renderer *renderer, cp_layer_t layer, cp_frame_t frame_encoder, cp_drawable_t drawable) { const cp_frame_timing_t timing_info = cp_drawable_get_frame_timing(drawable); const cp_time_t presentation_time = cp_frame_timing_get_presentation_time(timing_info); const CFTimeInterval target_render_time = cp_time_to_cf_time_interval(presentation_time); simd_float4x4 pose = matrix_identity_float4x4; const ar_pose_status_t status = ar_world_tracking_provider_query_pose_at_timestamp(renderer->world_tracking, target_render_time, renderer->pose); if (status == ar_pose_status_success) { pose = ar_pose_get_origin_from_device_transform(renderer->pose); } ... cp_drawable_set_ar_pose(drawable, renderer->pose); ... }
-
16:00 - Hand tracking joints
/ Hand tracking @available(xrOS 1.0, *) public struct Skeleton : @unchecked Sendable, CustomStringConvertible { public func joint(named: SkeletonDefinition.JointName) -> Skeleton.Joint public struct Joint : CustomStringConvertible, @unchecked Sendable { public var parentJoint: Skeleton.Joint? { get } public var name: String { get } public var localTransform: simd_float4x4 { get } public var rootTransform: simd_float4x4 { get } public var isTracked: Bool { get } } }
-
17:00 - Hand tracking with Render struct
// Hand tracking // Polling for hands struct Renderer { ar_hand_tracking_provider_t hand_tracking; struct { ar_hand_anchor_t left; ar_hand_anchor_t right; } hands; ... }; void renderer_init(struct Renderer *renderer) { ... ar_hand_tracking_configuration_t hand_config = ar_hand_tracking_configuration_create(); renderer->hand_tracking = ar_hand_tracking_provider_create(hand_config); ar_data_providers_t providers = ar_data_providers_create(); ar_data_providers_add_data_provider(providers, renderer->world_tracking); ar_data_providers_add_data_provider(providers, renderer->hand_tracking); ar_session_run(renderer->session, providers); renderer->hands.left = ar_hand_anchor_create(); renderer->hands.right = ar_hand_anchor_create(); ... }
-
17:25 - hand tracking call in render function
// Hand tracking // Polling for hands void render(struct Renderer *renderer, ... ) { ... ar_hand_tracking_provider_get_latest_anchors(renderer->hand_tracking, renderer->hands.left, renderer->hands.right); if (ar_trackable_anchor_is_tracked(renderer->hands.left)) { const simd_float4x4 origin_from_wrist = ar_anchor_get_origin_from_anchor_transform(renderer->hands.left); ... } ... }
-
18:00 - Demo app TimeForCube
// App @main struct TimeForCube: App { @StateObject var model = TimeForCubeViewModel() var body: some SwiftUI.Scene { ImmersiveSpace { RealityView { content in content.add(model.setupContentEntity()) } .task { await model.runSession() } .task { await model.processHandUpdates() } .task { await model.processReconstructionUpdates() } .gesture(SpatialTapGesture().targetedToAnyEntity().onEnded({ value in let location3D = value.convert(value.location3D, from: .global, to: .scene) model.addCube(tapLocation: location3D) })) } } }
-
18:50 - Demo app View Model
// View model @MainActor class TimeForCubeViewModel: ObservableObject { private let session = ARKitSession() private let handTracking = HandTrackingProvider() private let sceneReconstruction = SceneReconstructionProvider() private var contentEntity = Entity() private var meshEntities = [UUID: ModelEntity]() private let fingerEntities: [HandAnchor.Chirality: ModelEntity] = [ .left: .createFingertip(), .right: .createFingertip() ] func setupContentEntity() { ... } func runSession() async { ... } func processHandUpdates() async { ... } func processReconstructionUpdates() async { ... } func addCube(tapLocation: SIMD3<Float>) { ... } }
-
20:00 - function HandTrackingProvider
class TimeForCubeViewModel: ObservableObject { ... private let fingerEntities: [HandAnchor.Chirality: ModelEntity] = [ .left: .createFingertip(), .right: .createFingertip() ] ... func processHandUpdates() async { for await update in handTracking.anchorUpdates { let handAnchor = update.anchor guard handAnchor.isTracked else { continue } let fingertip = handAnchor.skeleton.joint(named: .handIndexFingerTip) guard fingertip.isTracked else { continue } let originFromWrist = handAnchor.transform let wristFromIndex = fingertip.rootTransform let originFromIndex = originFromWrist * wristFromIndex fingerEntities[handAnchor.chirality]?.setTransformMatrix(originFromIndex, relativeTo: nil) }
-
21:20 - function SceneReconstruction
func processReconstructionUpdates() async { for await update in sceneReconstruction.anchorUpdates { let meshAnchor = update.anchor guard let shape = try? await ShapeResource.generateStaticMesh(from: meshAnchor) else { continue } switch update.event { case .added: let entity = ModelEntity() entity.transform = Transform(matrix: meshAnchor.transform) entity.collision = CollisionComponent(shapes: [shape], isStatic: true) entity.physicsBody = PhysicsBodyComponent() entity.components.set(InputTargetComponent()) meshEntities[meshAnchor.id] = entity contentEntity.addChild(entity) case .updated: guard let entity = meshEntities[meshAnchor.id] else { fatalError("...") } entity.transform = Transform(matrix: meshAnchor.transform) entity.collision?.shapes = [shape] case .removed: meshEntities[meshAnchor.id]?.removeFromParent() meshEntities.removeValue(forKey: meshAnchor.id) @unknown default: fatalError("Unsupported anchor event") } } }
-
22:20 - add cube at tap location
class TimeForCubeViewModel: ObservableObject { func addCube(tapLocation: SIMD3<Float>) { let placementLocation = tapLocation + SIMD3<Float>(0, 0.2, 0) let entity = ModelEntity( mesh: .generateBox(size: 0.1, cornerRadius: 0.0), materials: [SimpleMaterial(color: .systemPink, isMetallic: false)], collisionShape: .generateBox(size: SIMD3<Float>(repeating: 0.1)), mass: 1.0) entity.setPosition(placementLocation, relativeTo: nil) entity.components.set(InputTargetComponent(allowedInputTypes: .indirect)) let material = PhysicsMaterialResource.generate(friction: 0.8, restitution: 0.0) entity.components.set(PhysicsBodyComponent(shapes: entity.collision!.shapes, mass: 1.0, material: material, mode: .dynamic)) contentEntity.addChild(entity) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。