当前位置:   article > 正文

【iOS ARKit】PhysicsBodyComponent

【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组件的最基本使用方法。

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号