• 【iOS ARKit】PhysicsBodyComponent


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

    1. //
    2. // PhysicsBodyView.swift
    3. // ARKitDeamo
    4. //
    5. // Created by zhaoquan du on 2024/3/14.
    6. //
    7. import SwiftUI
    8. import ARKit
    9. import RealityKit
    10. struct PhysicsBodyView: View {
    11. var body: some View {
    12. PhysicsBodyViewContainer().navigationTitle("物理模拟").edgesIgnoringSafeArea(.all)
    13. }
    14. }
    15. struct PhysicsBodyViewContainer:UIViewRepresentable {
    16. func makeCoordinator() -> Coordinator {
    17. Coordinator()
    18. }
    19. func makeUIView(context: Context) -> some ARView {
    20. let arView = ARView(frame: .zero)
    21. let config = ARWorldTrackingConfiguration()
    22. config.planeDetection = .horizontal
    23. context.coordinator.arView = arView
    24. arView.session.delegate = context.coordinator
    25. arView.session.run(config)
    26. return arView
    27. }
    28. func updateUIView(_ uiView: UIViewType, context: Context) {
    29. }
    30. class Coordinator: NSObject, ARSessionDelegate{
    31. var sphereEntity : ModelEntity!
    32. var arView:ARView? = nil
    33. let gameController = GameController()
    34. func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
    35. guard let anchor = anchors.first as? ARPlaneAnchor,
    36. let arView = arView else{
    37. return
    38. }
    39. let planeAnchor = AnchorEntity(anchor:anchor)
    40. //sample1
    41. let boxCollider: ShapeResource = .generateBox(size: [0.1,0.2,1])
    42. let box: MeshResource = .generateBox(size: [0.1,0.2,1], cornerRadius: 0.02)
    43. let boxMaterial = SimpleMaterial(color: .yellow,isMetallic: true)
    44. let boxEntity = ModelEntity(mesh: box, materials: [boxMaterial], collisionShape: boxCollider, mass: 0.05)
    45. boxEntity.physicsBody?.mode = .dynamic
    46. boxEntity.name = "Box"
    47. boxEntity.transform.translation = [0.2,planeAnchor.transform.translation.y+0.15,0]
    48. let sphereCollider : ShapeResource = .generateSphere(radius: 0.05)
    49. let sphere: MeshResource = .generateSphere(radius: 0.05)
    50. let sphereMaterial = SimpleMaterial(color:.red,isMetallic: true)
    51. sphereEntity = ModelEntity(mesh: sphere, materials: [sphereMaterial], collisionShape: sphereCollider, mass: 0.04)
    52. sphereEntity.physicsBody?.mode = .dynamic
    53. sphereEntity.name = "Sphere"
    54. sphereEntity.transform.translation = [-0.3,planeAnchor.transform.translation.y+0.15,0]
    55. sphereEntity.physicsBody?.material = .generate(friction: 0.001, restitution: 0.01)
    56. //平面
    57. let plane :MeshResource = .generatePlane(width: 1.2, depth: 1.2)
    58. let planeCollider : ShapeResource = .generateBox(width: 1.2, height: 0.01, depth: 1.2)
    59. let planeMaterial = SimpleMaterial(color:.gray,isMetallic: false)
    60. let planeEntity = ModelEntity(mesh: plane, materials: [planeMaterial], collisionShape: planeCollider, mass: 0.01)
    61. planeEntity.physicsBody?.mode = .static//静态平面不具备碰撞性
    62. planeEntity.physicsBody?.material = .generate(friction: 0.001, restitution: 0.1)
    63. planeAnchor.addChild(planeEntity)
    64. planeAnchor.addChild(boxEntity)
    65. planeAnchor.addChild(sphereEntity)
    66. //添加碰撞订阅
    67. let subscription = arView.scene.subscribe(to: CollisionEvents.Began.self, { event in
    68. print("box发生碰撞")
    69. print("entityA.name: \(event.entityA.name)")
    70. print("entityB.name: \(event.entityB.name)")
    71. print("Force : \(event.impulse)")
    72. print("Collision Position: \(event.position)")
    73. })
    74. gameController.collisionEventStreams.append(subscription)
    75. arView.scene.addAnchor(planeAnchor)
    76. let gestureRecognizers = arView.installGestures(.translation, for: sphereEntity)
    77. if let gestureRecognizer = gestureRecognizers.first as? EntityTranslationGestureRecognizer {
    78. gameController.gestureRecognizer = gestureRecognizer
    79. gestureRecognizer.removeTarget(nil, action: nil)
    80. gestureRecognizer.addTarget(self, action: #selector(handleTranslation))
    81. }
    82. arView.session.delegate = nil
    83. arView.session.run(ARWorldTrackingConfiguration())
    84. }
    85. @objc func handleTranslation(_ recognizer: EntityTranslationGestureRecognizer){
    86. guard let ball = sphereEntity else { return }
    87. let settings = gameController.settings
    88. if recognizer.state == .ended || recognizer.state == .cancelled {
    89. gameController.gestureStartLocation = nil
    90. ball.physicsBody?.mode = .dynamic
    91. return
    92. }
    93. guard let gestureCurrentLocation = recognizer.translation(in: nil) else { return }
    94. guard let gestureStartLocation = gameController.gestureStartLocation else {
    95. gameController.gestureStartLocation = gestureCurrentLocation
    96. return
    97. }
    98. let delta = gestureStartLocation - gestureCurrentLocation
    99. let distance = ((delta.x * delta.x) + (delta.y * delta.y) + (delta.z * delta.z)).squareRoot()
    100. if distance > settings.ballPlayDistanceThreshold {
    101. gameController.gestureStartLocation = nil
    102. ball.physicsBody?.mode = .dynamic
    103. return
    104. }
    105. //ball.physicsBody?.mode = .kinematic
    106. let realVelocity = recognizer.velocity(in: nil)
    107. let ballParentVelocity = ball.parent!.convert(direction: realVelocity, from: nil)
    108. var clampedX = ballParentVelocity.x
    109. var clampedZ = ballParentVelocity.z
    110. // 夹断
    111. if clampedX > settings.ballVelocityMaxX {
    112. clampedX = settings.ballVelocityMaxX
    113. } else if clampedX < settings.ballVelocityMinX {
    114. clampedX = settings.ballVelocityMinX
    115. }
    116. // 夹断
    117. if clampedZ > settings.ballVelocityMaxZ {
    118. clampedZ = settings.ballVelocityMaxZ
    119. } else if clampedZ < settings.ballVelocityMinZ {
    120. clampedZ = settings.ballVelocityMinZ
    121. }
    122. let clampedVelocity: SIMD3<Float> = [clampedX, 0.0, clampedZ]
    123. //ball.physicsMotion?.linearVelocity = clampedVelocity
    124. ball.addForce(clampedVelocity*0.1, relativeTo: nil)
    125. }
    126. }
    127. }
    128. #Preview {
    129. PhysicsBodyView()
    130. }

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

       (1)构建模拟环境。

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

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

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

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

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

  • 相关阅读:
    【Vue3从零开始-实战】S14:详情页回退事件及路由参数的传递获取数据
    C#异步编程由浅入深(三)细说Awaiter
    IDLE、Anaconda安装与使用
    图的深度优先遍历
    spring自动装配servlet
    【Python】将Python中的多维列表进行展开
    MySql中的锁
    标准化数据模型
    Vue学习笔记
    暑假超越计划练习题
  • 原文地址:https://blog.csdn.net/weixin_37543822/article/details/136745258