【iOS ARKit】触发器与触发域

触发器

     在上节的示例中,所有可见的物体都参与了物理模拟,但在一些应用中,我们物理模拟,同时又需要了解是否有物体与它们发生了碰撞。如在 AR场景中,当角色靠近一散门时,我们并不希望因为角色与门发生碰撞而导致门移动,但又需要了解是否有角色与门发生了碰撞并以此为依据决定是否打开门。在这种应用场合中,使用触发器是最好的选择。

     在 RealityKit 中使用触发器非常简单,具体在使用时,只需要将物体的 physicsBody. mode设置为static,并将 collision. mode 设置为 trigger即可,这样既能防止物体产生运动又能捕获到碰撞相关信息。

     修改后,运行应用,操作球体与长方体发生碰撞,在碰撞发生后,可以看到相应的碰撞信息依然会打印出来,但由于长方体 physicsBody. mode 属性设置 static,长方体不会参与物理模拟,也不会发生移动。

 触发域

     使用触发器的方式适合于对可见物体进行碰撞检测,在实际应用开发中,还有一种情况,对不可见物体的碰撞检测,如在 AR 游戏中,当角色进入某一空间后触发新的机关或者激活 AI Agent(NPC,Non-PlayerCharacter,智能体)。对于这种情况,我们可以建一个 ModeIEntity,但是不渲染相应网格,就像上节代码四周围栏所做的那样。但在 RealityKit 中提供了另一种更简单易用的应对这种情况的实体类,它就是 TriggerVolume。Trigger Volume(触发区域体)实体类包含 Transform component、 Synchronizationcomponent、Collision component3 个组件。

     触发域实体包含 Collision component 组件,能够与其他碰撞体发生碰撞,因此,我们可以将触发域实体作为一个 传感器使用,当有其他碰撞体进入或者离开触发域实体所占空间时实时地获取相应消息。与其他带碰撞器的实体一样,当有其他碰撞体进入或者离开触发域实体时也会触发 CollisionEvents,我们可以通过订阅这些事件进行相应处理。

     触发域实体也是一个实体,但它非常简单,因为不带有网格信息,因此无法对它的触发域实体进行渲染。触发域实体也不参与物理模拟,但将其作为碰撞检测非常高效。

     触发域实体的使用与其他实体的使用一样,我们对代码进行改造,将 boxEntity 换成TriggerVolume,关键代码如下所示。

//
//  TriggerVolumeView.swift
//  ARKitDeamo
//
//  Created by zhaoquan du on 2024/3/18.
//import SwiftUI
import ARKit
import RealityKitstruct TriggerVolumeView: View {var body: some View {TriggerVolumeContentView().navigationTitle("触发器与触发域").edgesIgnoringSafeArea(.all)}
}struct TriggerVolumeContentView: UIViewRepresentable{func makeCoordinator() -> Coordinator {Coordinator()}func makeUIView(context: Context) -> some ARView {let arView = ARView(frame: .zero)let config = ARWorldTrackingConfiguration()config.planeDetection = .horizontalcontext.coordinator.arView = arViewarView.session.delegate  = context.coordinatorarView.session.run(config)return arView}func updateUIView(_ uiView: UIViewType, context: Context) {}class Coordinator: NSObject, ARSessionDelegate{var sphereEntity : ModelEntity!var arView:ARView? = nillet gameController = GameController()@MainActor func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {guard let anchor = anchors.first as? ARPlaneAnchor,let arView = arView else{return}let planeAnchor = AnchorEntity(anchor:anchor)let triggerShape: ShapeResource = .generateBox(size: [01,0.2,0.3])let triggerVolume = TriggerVolume(shape: triggerShape)triggerVolume.name = "TriggerVolum"triggerVolume.transform.translation = [0.2,planeAnchor.transform.translation.y + 0.15,0]let sphereMaterial = SimpleMaterial(color:.red,isMetallic: true)sphereEntity = ModelEntity(mesh: .generateSphere(radius: 0.05),materials: [sphereMaterial], collisionShape: .generateSphere(radius: 0.05), mass: 0.04)sphereEntity.physicsBody?.mode =  .dynamicsphereEntity.name = "Sphere"sphereEntity.transform.translation = [-0.3,planeAnchor.transform.translation.y+0.15,0]sphereEntity.physicsBody?.material = .generate(friction: 0.001,restitution: 0.01)let plane :MeshResource = .generatePlane(width: 1.2, depth: 1.2)let planeCollider : ShapeResource = .generateBox(width: 1.2, height: 0.01, depth: 1.2)let planeMaterial = SimpleMaterial(color:.gray,isMetallic: false)let planeEntity = ModelEntity(mesh: plane, materials: [planeMaterial], collisionShape: planeCollider, mass: 0.01)planeEntity.physicsBody?.mode = .static//静态平面planeEntity.physicsBody?.material = .generate(friction: 0.001, restitution: 0.1)planeAnchor.addChild(planeEntity)planeAnchor.addChild(triggerVolume)planeAnchor.addChild(sphereEntity)let subscription = arView.scene.subscribe(to: CollisionEvents.Began.self,on: triggerVolume) { event inprint("trigger volume发生碰撞")print("EntityA name : \(event.entityA.name)")print("EntityB name : \(event.entityB.name)")print("Force : \(event.impulse)")print("Collision Position: \(event.position)")}gameController.gameAnchor = try! Ball.loadBallGame()gameController.collisionEventStreams.append(subscription)arView.scene.addAnchor(planeAnchor)let gestureRecognizers = arView.installGestures(.translation, for: sphereEntity)if let gestureRecognizer = gestureRecognizers.first as? EntityTranslationGestureRecognizer {gameController.gestureRecognizer = gestureRecognizergestureRecognizer.removeTarget(nil, action: nil)gestureRecognizer.addTarget(self, action: #selector(self.handleTranslation))}arView.session.delegate = nilarView.session.run(ARWorldTrackingConfiguration())}@objcfunc handleTranslation(_ recognizer: EntityTranslationGestureRecognizer) {guard let ball = sphereEntity else { return }let settings = gameController.settingsif recognizer.state == .ended || recognizer.state == .cancelled {gameController.gestureStartLocation = nilball.physicsBody?.mode = .dynamicreturn}guard let gestureCurrentLocation = recognizer.translation(in: nil) else { return }guard let gestureStartLocation = gameController.gestureStartLocation else {gameController.gestureStartLocation = gestureCurrentLocationreturn}let delta = gestureStartLocation - gestureCurrentLocationlet distance = ((delta.x * delta.x) + (delta.y * delta.y) + (delta.z * delta.z)).squareRoot()if distance > settings.ballPlayDistanceThreshold {gameController.gestureStartLocation = nilball.physicsBody?.mode = .dynamicreturn}ball.physicsBody?.mode = .kinematiclet realVelocity = recognizer.velocity(in: nil)let ballParentVelocity = ball.parent!.convert(direction: realVelocity, from: nil)var clampedX = ballParentVelocity.xvar clampedZ = ballParentVelocity.z// 夹断if clampedX > settings.ballVelocityMaxX {clampedX = settings.ballVelocityMaxX} else if clampedX < settings.ballVelocityMinX {clampedX = settings.ballVelocityMinX}// 夹断if clampedZ > settings.ballVelocityMaxZ {clampedZ = settings.ballVelocityMaxZ} else if clampedZ < settings.ballVelocityMinZ {clampedZ = settings.ballVelocityMinZ}let clampedVelocity: SIMD3<Float> = [clampedX, 0.0, clampedZ]ball.physicsMotion?.linearVelocity = clampedVelocity}}
}#Preview {TriggerVolumeView()
}

    运行上述代码,在加载后的场景中无法看到触发域实体对象,使用移动手势操作球体,当球体经过触发域实体所在区域时,碰撞被检测到,CollisionEvents.Began 事件被触发,相应信息也被打印出来。在本章所有演示示例中,我们只对碰撞发生的Began事件进行了处理,CollisionEvents 事件其实包括3个事件,具体如下表。

事件名称

描述

CollisionEvents. Began

结构体,当两个碰撞体开始接触时触发,这个事件在每次碰撞中只触发一次

CollisionEvents. Updated

结构体,当两个碰撞体保持接触时,这个事件在每一帧都会触发

CollisionEvents. Ended

结构体,当两个碰撞体脱离接触时触发,这个事件在每次碰撞中只触发一次

    通过这3个事件,就能方便地处理所有与碰撞相关的事务,关于 RealityKit 中事件的处理,可参阅之前相关章节。

自定义物理实体类

     我们通过实体的 PhysicsBodyComponent 组件和 PhysicsMotionComponent 组件实现了物理模拟,在 RealityKit 中,ModelEntity实体类默认带有这两个组件,使用ModelEntity 类创建的实体都可以与物理模拟。在 RealityKit 中,使用物理引擎进行物理模拟的类必须遵循相应的物理协议,根据物理模拟类协议的层级结构,我们可以通过遵循HasPhysicsBody、HasPhysicsMotion协议或直接通过遵循HasPhysics协议,自定义物理实体类。自定义物理实体类后,可以更方便灵活地进行物理模拟,并简化代码。

物理引擎总结

      物理引擎突破了按照预定脚本执行物体运动计算的方式,通过设置物体的物理参数来运行。使用物理引擎后,虚拟物体之间、虚拟物体与现实环境之间的相互作用不需要进行硬编码,而是按照牛顿运动定律实时计算模拟,由于牛顿运动定律的客观性,这种模拟出来的效果与真实物体间相互作用效果可以做到完全一致,从而大大增强虚拟物体的可信度。

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

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

相关文章

自然语言处理学习总结

目录 1、词表示 2、语言模型&#xff08;LM&#xff09; 3、常用学习网址 自然语言处理 1、词表示 词表示&#xff1a;自然语言中最基本的语言单位表示成机器理解的方式 方式一&#xff1a;词与词之间的相似度 方式二&#xff1a;词与词之间的关系 词义的表示方法&…

云手机在海外电商中的应用优势

随着海外市场的不断拓展&#xff0c;电商行业对于高效、安全的工具需求日益增长。在这一背景下&#xff0c;云手机作为一种新型服务&#xff0c;为海外电商提供了强大的支持和便利。云手机对传统物理手机起到了非常好的延展和补充作用&#xff0c;拓展了更广泛的应用场景&#…

Kafka整理-Kafka与传统消息队列系统(如RabbitMQ, ActiveMQ)的区别是什么?

Apache Kafka与传统消息队列系统(如RabbitMQ, ActiveMQ)虽然都是处理消息和数据流的中间件,但它们在设计理念、架构、功能和使用场景方面有显著的区别。下面是Kafka与传统消息队列系统的主要区别: 1. 设计目的和使用场景 Kafka: 设计初衷是为处理大量的实时数据流。强调高…

埃隆·马斯克与OpenAI诉讼案剖析

解析马斯克与OpenAI的法律纠纷 摘要 在人工智能迅速发展的背景下&#xff0c;一场法律纠纷引起了广泛关注&#xff0c;它涉及到了理想主义与商业现实的交汇点。特斯拉创始人埃隆马斯克起诉了他共同创立的人工智能研究组织OpenAI。马斯克认为OpenAI背离了其最初的宗旨&#xf…

RabbitMQ问题

如何实现顺序消费&#xff1f; 消息放入到同一个队列中消费 如何解决消息不丢失&#xff1f; 方案&#xff1a; 如上图&#xff1a;消息丢失有三种情况&#xff0c;解决了以上三种情况就解决了丢失的问题 1、丢失1--->消息在到达交换机的时候&#xff1b;解决&#xff1…

原生html vue3使用element plus 的树tree上移下移案例源码

上效果 html源码 <!DOCTYPE html> <html lang"en"> <!-- * Name: mallSalesReports.html * Description: * Author Lani * date 2024-02-28 18:32:36 --> <head><meta charset"UTF-8"><meta name"viewport" …

docker入门(五)—— 小练习,docker安装nginx、elasticsearch

练习 docker 安装 nginx # 搜素镜像 [rootiZbp15293q8kgzhur7n6kvZ home]# docker search nginx NAME DESCRIPTION STARS OFFICIAL nginx …

计算机网络——协议层次及服务模型

计算机网络中的协议层次是指将网络功能划分为不同的层次&#xff0c;每个层次负责特定的功能&#xff0c;并通过协议进行通信。 一、为什么要分层 分层是设计/讨论复杂系统的有效方法。分层使得复杂系统概念化&#xff0c;结构清晰便于标示网路组件&#xff0c;以及描述其相互…

【Postrsql】postgresql的介绍、安装和使用

介绍 1.基本信息 PostgreSQL是一个功能强大的开源关系型数据库系统。经过长达15年以上的积极开发和不断改进&#xff0c;PostgreSQL已在可靠性、稳定性、数据一致性等获得了业内极高的声誉。目前PostgreSQL可以运行在所有主流操作系统上&#xff0c;包括Linux、Unix和Windows…

html5cssjs代码 024 响应式布局示例

html5&css&js代码 024 响应式布局示例 一、代码二、解释 该HTML代码重点在于构建一个带有响应式设计的两栏布局网页&#xff0c;包含页头、导航条、主要内容区&#xff08;左右两列&#xff09;和底部区域&#xff0c;并运用CSS样式设置页面元素的布局、颜色、字体、间…

【Node.js从基础到高级运用】十五、单元测试与集成测试

引言 在Node.js开发过程中&#xff0c;测试是确保代码质量和功能正确性的关键步骤。单元测试和集成测试是最常见的测试类型。下面我们将使用Jest框架来进行测试。 单元测试 单元测试是指对软件中的最小可测试单元进行检查和验证。在Node.js中&#xff0c;这通常指的是函数或者…

安卓面试题多线程 86-90

86. 请列举ThreadPoolexecutor参数配置?corePoolSize- 池中所保存的线程数,包括空闲线程。 maximumPoolSize - 池中允许的最⼤线程数。 keepAliveTime当线程数⼤于核⼼时,此为终⽌前多余的空闲线程等待新任务的最长时间。 unit - keepAliveTime 参数的时间单位。 workQueue …

HarmonyOS开发:超详细介绍如何开源静态共享包,实现远程依赖

前言 当我们开发了一个独立的功能&#xff0c;想让他人进行使用&#xff0c;一般的方式就是开源出去&#xff0c;有源码的方式&#xff0c;也有文件包的形式&#xff0c;当然了也有远程依赖的方式&#xff0c;比如在Android中&#xff0c;我们可以提供源码&#xff0c;也可以打…

SQLiteC/C++接口详细介绍sqlite3_stmt类(一)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍sqlite3_stmt类简介 下一篇&#xff1a;SQLiteC/C接口详细介绍sqlite3_stmt类&#xff08;二&#xff09; ​ 序言&#xff1a; 本文开始了SQLite的第二个类的详细介绍…

Qt 容器类控件

Group Box 使用 QGroupBox 实现一个带有标题的分组框可以把其他的控件放到里面作为一组&#xff0c;这样看起来能更好看一点. 核心属性 属性说明title分组框的标题alignment分组框内部内容的对齐方式flat是否是 “扁平” 模式checkable是否可选择. 设为 true&#xff0c;则在…

2024年华为OD机试真题-石头剪刀布游戏-Java-OD统一考试(C卷)

题目描述: 石头剪刀布游戏有3种出拳形状:石头、剪刀、布。 分别用字母 A,B,C表示。 游戏规则: 1)出拳形状之间的胜负规则如下:A> B;B> C;C> A “>” 左边一个字母,表示相对优势形状。 右边一个字母,表示相对劣势形状。 2) 当本场次中有且仅有一种出拳形状…

鸿蒙Harmony应用开发—ArkTS-高级组件:@ohos.advertising.AdComponent (非全屏广告展示组件))

本模块提供展示非全屏广告的能力。 说明&#xff1a; 本模块首批接口从API Version 11开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import { AdComponent } from ohos.advertising.AdComponent; AdComponent AdComponent(ads: Ar…

webpack5零基础入门-12搭建开发服务器

1.目的 每次写完代码都需要手动输入指令才能编译代码&#xff0c;太麻烦了&#xff0c;我们希望一切自动化 2.安装相关包 npm install --save-dev webpack-dev-server 3.添加配置 在webpack.config.js中添加devServer相关配置 /**开发服务器 */devServer: {host: localhos…

C# 使用ffmpeg将图片保存为mp4视频

使用 FFmpeg 这个强大的多媒体处理工具&#xff0c;可以轻松地将一系列图片转换为一个 MP4 视频文件。以下是一个基本的命令行示例来完成这个任务&#xff1a; ffmpeg -framerate 25 -i image-%03d.jpg -c:v libx264 -r 30 -pix_fmt yuv420p output.mp4 命令参数说明&#xf…

华为中心AP 配置入侵防御实验

配置入侵防御示例 组网图形 图1 入侵防御组网图 组网需求配置思路操作步骤中心AP的配置文件 组网需求 如图1所示&#xff0c;某企业部署了WLAN网络&#xff0c;内网用户可以访问Internet的Web服务器。现需要在中心AP上配置入侵防御功能&#xff0c;具体要求如下&#xff1a; 保…