【iOS ARKit】PhysicsBodyComponent

news2025/1/11 17:00:57

     在学习完 RealityKit 进行物理模拟的相关理论知识后,下面通过使用 PhysicsBodyComponent 组件进行物理模拟演示,主要代码如下所示,稍后对代码进行详细解析。

//
//  PhysicsBodyView.swift
//  ARKitDeamo
//
//  Created by zhaoquan du on 2024/3/14.
//

import SwiftUI
import ARKit
import RealityKit

struct PhysicsBodyView: View {
    var body: some View {
        PhysicsBodyViewContainer().navigationTitle("物理模拟").edgesIgnoringSafeArea(.all)
    }
}
struct PhysicsBodyViewContainer:UIViewRepresentable {
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    
    
    func makeUIView(context: Context) -> some ARView {
        let arView = ARView(frame: .zero)
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = .horizontal
        
        context.coordinator.arView = arView
        
        arView.session.delegate  = context.coordinator
        arView.session.run(config)
        
        return arView
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
        
    }
    
    
    class Coordinator: NSObject, ARSessionDelegate{
        var sphereEntity : ModelEntity!
        var arView:ARView? = nil
        let gameController = GameController()
        
        func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
            guard let anchor = anchors.first as? ARPlaneAnchor,
                  let arView = arView else{
                return
            }
            let planeAnchor = AnchorEntity(anchor:anchor)
            
            //sample1
            let boxCollider: ShapeResource = .generateBox(size: [0.1,0.2,1])
            let box: MeshResource = .generateBox(size: [0.1,0.2,1], cornerRadius: 0.02)
            let boxMaterial = SimpleMaterial(color: .yellow,isMetallic: true)
            let boxEntity = ModelEntity(mesh: box, materials: [boxMaterial], collisionShape: boxCollider, mass: 0.05)
            boxEntity.physicsBody?.mode = .dynamic
            boxEntity.name = "Box"
            boxEntity.transform.translation = [0.2,planeAnchor.transform.translation.y+0.15,0]
            
            let sphereCollider : ShapeResource = .generateSphere(radius: 0.05)
            let sphere: MeshResource = .generateSphere(radius: 0.05)
            let sphereMaterial = SimpleMaterial(color:.red,isMetallic: true)
            sphereEntity = ModelEntity(mesh: sphere, materials: [sphereMaterial], collisionShape: sphereCollider, mass: 0.04)
            sphereEntity.physicsBody?.mode = .dynamic
            sphereEntity.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(boxEntity)
            planeAnchor.addChild(sphereEntity)
            
            //添加碰撞订阅
            let subscription = arView.scene.subscribe(to: CollisionEvents.Began.self, { event in
                print("box发生碰撞")
                print("entityA.name: \(event.entityA.name)")
                print("entityB.name: \(event.entityB.name)")
                print("Force : \(event.impulse)")
                print("Collision Position: \(event.position)")
            })
            gameController.collisionEventStreams.append(subscription)
            
            arView.scene.addAnchor(planeAnchor)
            let gestureRecognizers = arView.installGestures(.translation, for: sphereEntity)
            if let gestureRecognizer = gestureRecognizers.first as? EntityTranslationGestureRecognizer {
                gameController.gestureRecognizer = gestureRecognizer
                gestureRecognizer.removeTarget(nil, action: nil)
                gestureRecognizer.addTarget(self, action: #selector(handleTranslation))
            }
            arView.session.delegate = nil
            arView.session.run(ARWorldTrackingConfiguration())
        }
        @objc func handleTranslation(_ recognizer: EntityTranslationGestureRecognizer){
            guard let ball = sphereEntity else { return }
        
            let settings = gameController.settings
            if recognizer.state == .ended || recognizer.state == .cancelled {
                gameController.gestureStartLocation = nil
                ball.physicsBody?.mode = .dynamic
                return
            }
            guard let gestureCurrentLocation = recognizer.translation(in: nil) else { return }
            guard let gestureStartLocation = gameController.gestureStartLocation else {
                gameController.gestureStartLocation = gestureCurrentLocation
                return
            }
            let delta = gestureStartLocation - gestureCurrentLocation
            let distance = ((delta.x * delta.x) + (delta.y * delta.y) + (delta.z * delta.z)).squareRoot()
            if distance > settings.ballPlayDistanceThreshold {
                gameController.gestureStartLocation = nil
                ball.physicsBody?.mode = .dynamic
                return
            }
            //ball.physicsBody?.mode = .kinematic
            let realVelocity = recognizer.velocity(in: nil)
            let ballParentVelocity = ball.parent!.convert(direction: realVelocity, from: nil)
            var clampedX = ballParentVelocity.x
            var 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
         
            ball.addForce(clampedVelocity*0.1, relativeTo: nil)
        }
    }
}

#Preview {
    PhysicsBodyView()
}

      在代码清单中,实现的功能如下:

   (1)构建模拟环境。

   (2)通过施加力,对物体运动进行物理模拟。

     在功能1中,我们通过 session(_ session:ARSession, didAdd anchors: [ARAnchor])方法对平面检测情况进行监视,当 ARKit 检测到符合要求的水平平面后,手动生成一个长方体、一个球体、一个承载这两个物体的平面,构建了基本的模拟环境,如图所示。由于生成的长方体与球体均是带有质量与碰撞器的实体,在使用物理引擎时,它们会在重力作用下下坠,生成的平面主要用于承载这两个物体。在设置好物理模拟相关属性后,我们还订阅(subscriptions)了长方体的碰撞事件,当长方体与其他物体发生碰撞时会打印出发生碰撞的两个实体对象名称、碰撞时的受力和碰撞位置信息。

     在功能2中,为方便控制,我们使用了 RealityKit 中的平移手势(Entity Lranslation GrestureRecogniner),通过计算使用者手指在犀幕上滑动的速度生威作用力,并将该作用力施加在球体上,通过施加作用力就可以观察球体与长方体在物理引擎作用下的运动效果(为防止施加的力过大,我们使用了GameSettings结构体并定义了几个边界值,具体可以参看本节源码)。

     编译后测试,使用平移手势操作球体,当球体撞击到长方体后,会发生物理交互并触发长方体的碰撞事件。读者可以修改使用不同的物理参数和碰撞形状,看一看物理参数如何影响物体的运动,以及碰撞形状如何影响碰撞位置。

     这个例子综合演示了物理参数和属性的设置、物理事件的处理、物理材质对物理模拟的影响,同时也是最简单的物理引擎使用案例,没有使用 group 和 mask设置碰撞分组,仅演示了 PhysicsBodyComponent组件的最基本使用方法。

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

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

相关文章

禁止ie自动跳转edge

因为微软对ie已经彻底停止维护了&#xff0c;对于没有升级系统的用户来说&#xff0c;会自动更新edge然后将ie给禁止使用。下面方法有效的解决windows10下&#xff0c;禁止ie自动跳转edge。 方法一&#xff1a;对于2023年10月份前的更新可用 打开控制面板&#xff0c;点击网络…

一个能够自我游戏的贪吃蛇(pygame与搜索算法)

贪吃蛇小游戏再经典不过了&#xff0c;作为编程爱好者&#xff0c;代码编译的贪吃蛇&#xff0c;又能有怎样的成绩呢&#xff1f; 带着好奇&#xff0c;开始&#xff01; 先做一个普通的贪吃蛇游戏 引入相关package import pygame 定义相关配置变量 # 定义字体 font pyg…

Java 根据IP获取IP地址信息(离线)

<!-- https://mvnrepository.com/artifact/org.lionsoul/ip2region --><dependency><groupId>org.lionsoul</groupId><artifactId>ip2region</artifactId><version>2.7.0</version></dependency> 地址&#xff1a;http…

DBA面试题:MySQL缓存池LRU算法做了哪些改进?

下图是MySQL&#xff08;MySQL5.7版本&#xff09;体系架构图 MySQL的InnoDb Buffer Pool 缓冲池是主内存中的一个区域&#xff0c;用来缓存InnoDB在访问表和索引时的数据。对于频繁使用的数据可以直接从内存中访问&#xff0c;从而加快处理速度。如果一台服务器专用作MySQL数据…

逻辑数据平台的 NoETL 之道(内含QA)

作者简介&#xff1a; 余俊&#xff0c;Aloudata 合伙人 & 技术副总裁。拥有 18 年互联网技术和大数据平台相关架构经验。作为主架构师及核心研发主导并完成了 Alibaba B2B 首个海量分布式 KV 存储系统&#xff0c;作为网站架构师负责 Aliexpress 全球买全球卖交易系统的第…

【四 (1)数据可视化之如何选用正确的图表】

目录 文章导航一、数据分析中可视化的作用1、揭示数据关联和模式2、支持数据分析和决策3、提升沟通和共享效果4、强调关键信息和发现5、增强故事叙述和记忆效果6、有效增强数据交互性数据7、复杂信息易理解8、数据多维度显示 二、如何选用合适的图表1、简洁性避免使用过于复杂或…

Adobe PDF背景设置护眼模式,缓解眼部疲劳

一、背景 在用Adobe PDF看论文时&#xff0c;默认的白色背景看久了&#xff0c;眼睛会特别疲劳&#xff0c;下面介绍如何设置背景为护眼模式。 二、设置PDF为护眼模式 使用Adobe Acrobat Pro DC打开任意PDF文件&#xff0c;在上方工具栏选择“编辑”&#xff0c;在下拉菜单栏…

VS2022一个项目中运行多个c++程序

VS2022一个项目中运行多个c程序设置 问题情况解决 问题 一般使用vs2022都需要配置好一些路径依赖&#xff0c;但一个项目中只能使用一个源文件&#xff0c;这也是为了避免找不到那些依赖&#xff0c;可是我们就是想为了可以快速编写&#xff0c;而不是浪费在那些配置环境的时间…

基于java实用的音乐软件微信小程序的设计与实现【附项目源码】分享

基于实用的音乐软件微信小程序的设计与实现: 源码地址&#xff1a;https://download.csdn.net/download/weixin_43894652/88842586 一、引言 随着移动互联网的普及和微信小程序的兴起&#xff0c;音乐类小程序成为了用户随时随地享受音乐的重要工具。本需求文档旨在详细阐述一…

mac安装rust开发环境,使用brew安装和全局配置

mac下使用brew可以一键安装环境&#xff1a; brew install rustup 安装完成执行&#xff1a; rustup-init 按照提示配置即可&#xff1a; 出现&#xff1a; 想要全局生效&#xff1a; echo export PATH"$HOME/.cargo/bin:$PATH" >> ~/.bash_profile source…

企业级授权源码 – 高价值企业授权系统,内含授权系统、工单系统和盗版检测功能

企业授权系统功能简介&#xff1a; 1、网站管理&#xff1a;包括基本管理、系统设置、公告设置、接口设置、价格设置和下载设置等。 2、内容管理&#xff1a;包括文章管理和广告轮图管理&#xff0c;以及添加授权、授权列表和授权日志等。 3、订单管理&#xff1a;包括支付订…

iOS 腾讯Pag动画框架-实现PagView的截图功能

背景 产品想要一个首页的截图功能&#xff0c;一听这个功能&#xff0c;心想那还不简单&#xff0c;将父视图控件转换成图片保存就行了。按照这个思路实现&#xff0c;很快就打脸啦&#xff0c;首页的这些动画一个都没有截出来&#xff0c;就像消失啦似的。然后蠢蠢的将动画暂…

STM32初识2

复位和时钟控制&#xff08;RCC&#xff1a;reset clock control&#xff09; 系统复位 当发生以下任一事件时&#xff0c;产生一个系统复位&#xff1a; 1. NRST 引脚上的低电平 ( 外部复位 ) 2. 窗口看门狗计数终止 (WWDG 复位 ) 3. 独立看门狗计数终止 (IWDG 复位 ) …

4、鸿蒙学习-@ohos.promptAction (弹窗)

创建并显示文本提示框、对话框和操作菜单。 说明 本模块首批接口从API version 9开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 该模块不支持在UIAbility的文件声明处使用&#xff0c;即不能在UIAbility的生命周期中调用&#xff0c;需要在创建…

云计算 3月14号 (TCP三次握手和四次挥手)

1.TCP三次握手和四次挥手 1.TCP的传输过程&#xff1a; Seq 序列号 保障传输过程可靠。 ACK &#xff08;确认消息&#xff09; SYN &#xff08;在建立TCP连接的时候使用&#xff09; FIN &#xff08;在关闭TCP连接的时候使用&#xff09; 3.TCP建立连接的过程&…

3.Gen<I>Cam文件配置

Gen<I>Cam踩坑指南 我使用的是大恒usb相机&#xff0c;第一步到其官网下载大恒软件安装包,安装完成后图标如图所示&#xff0c;之后连接相机&#xff0c;打开软件&#xff0c;相机显示一切正常。之后查看软件的安装目录如图&#xff0c;发现有GenICam和GenTL两个文件&am…

arcgis pro植被冠层分析及单木识别

测试正射影像和点云数据介绍(文末分享):点云数据每平方米包含 0.5-1 个点。准备的课程如下;地面、水、桥或未分类。两个数据的最小单位覆盖面积为 2.5 x 2.5 公里。两个数据均位于 SWEREF 99 TM 坐标系中。正射影像数据由 RGBI(红、绿、蓝和近红外)波段组成。两个数据的空…

Spark杂谈

文章目录 什么是Spark对比HadoopSpark应用场景Spark数据处理流程什么是RDDSpark架构相关进程入门案例&#xff1a;统计单词数量Spark开启historyServer 什么是Spark Spark是一个用于大规模数据处理的统一计算引擎Spark一个重要的特性就是基于内存计算&#xff0c;从而它的速度…

计算机网络——物理层(奈氏准则和香农定理)

计算机网络——物理层&#xff08;奈氏准则和香农定理&#xff09; 失真码间串扰奈氏准则&#xff08;奈奎斯特定理&#xff09;极限数据率 噪声信噪比香农定理奈氏准则和香农定理的区别 前面我们已经了解一些数据通信的基本知识&#xff0c;没有看过上一篇得小伙伴可以点击这里…

老电脑装什么系统流畅

对于一些老旧电脑来说&#xff0c;重装系统是提升电脑性能的最佳选择。那么&#xff0c;老电脑装什么系统流畅呢&#xff1f;推荐Windows 7系统&#xff0c;它对硬件的需求相对较低。配置较低的电脑运行Windows 7可以更好地利用系统资源&#xff0c;提高电脑的运行速度和响应能…