Machineboy空

RealityKit 입문 - Physics 본문

카테고리 없음

RealityKit 입문 - Physics

안녕도라 2024. 10. 14. 22:49

PhysicsBodyComponent, generateCollisionShapes

func makeUIView(context: Context) -> ARView {
        
        let arView = ARView(frame: .zero)
        
        /// 땅에 콜라이더 설치
        let planeAnchorEntity = AnchorEntity(plane: .horizontal)
        let plane = ModelEntity(mesh: MeshResource.generatePlane(width: 1, height: 1), materials: [SimpleMaterial(color: .orange, isMetallic: true)])
        
        plane.transform.rotation = simd_quatf(angle: .pi / 2, axis: SIMD3(1,0,0))
        
        /// static: 떨어지지 않음
        plane.physicsBody = PhysicsBodyComponent(massProperties: .default,material: .generate(), mode: .static)
        plane.generateCollisionShapes(recursive: true)
        
        planeAnchorEntity.addChild(plane)
        arView.scene.addAnchor(planeAnchorEntity)
        
        arView.addGestureRecognizer(UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap)))
        
        context.coordinator.view = arView
      
        
        return arView
    }
class Coordinator: NSObject {
    
    weak var view: ARView?
    
    @objc func handleTap(_ recognizer: UITapGestureRecognizer) {
        
        guard let view = view else { return }
        let location =  recognizer.location(in: view)
        
        let results = view.raycast(from: location, allowing: .estimatedPlane, alignment: .horizontal)
        
        if let result = results.first {
            
            let anchorEntity = AnchorEntity(raycastResult: result)
            let box = ModelEntity(mesh: MeshResource.generateBox(size: 0.3), materials: [SimpleMaterial(color: .green, isMetallic: false)])
            
            /// default는 1m지름의 sphere로 약 1kg 정도임
            /// dynamic, static, kinematic
            /// static: moved될 수 없다는 뜻이다.
            /// dynamic: 움직일 수 있다.
            
            box.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .generate(), mode: .dynamic)
            box.generateCollisionShapes(recursive: true)
            
            box.position = simd_make_float3(0,0.7,0)
            
            anchorEntity.addChild(box)
            view.scene.anchors.append(anchorEntity)
            
            
        }
    }
}

 


Collision Detection (1) - CollisionEvents.Began, Updated, Ended

/// 투명한 바닥 콜라이더 설치

let floorAnchor = AnchorEntity(plane: .horizontal)
        let floor = ModelEntity(mesh: MeshResource.generateBox(size: [1000,0,1000]), materials: [OcclusionMaterial()])
        
        floor.generateCollisionShapes(recursive: true)
        floor.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .static)
        
        floorAnchor.addChild(floor)
        arView.scene.addAnchor(floorAnchor)
// CollisionEvent - Began과 Ended 체크

class Coordinator: NSObject, ARSessionDelegate {
    
    weak var view: ARView?
    var collisionSubscriptions = [Cancellable]()
    
    @objc func handleTap(_ recognizer: UITapGestureRecognizer) {
        
        guard let view = view else { return }
        let location =  recognizer.location(in: view)
        
        let results = view.raycast(from: location, allowing: .estimatedPlane, alignment: .horizontal)
        
        if let result = results.first {
            
            let anchorEntity = AnchorEntity(raycastResult: result)
            let box = ModelEntity(mesh: MeshResource.generateBox(size: 0.2), materials: [SimpleMaterial(color: .green, isMetallic: false)])
            box.position.y = 0.3
            box.generateCollisionShapes(recursive: true)
            
            box.physicsBody = PhysicsBodyComponent(massProperties: .default, mode: .dynamic)
            
            /// mode : trigger
            /// filter: sensor = particular model entity can collide
            box.collision = CollisionComponent(shapes: [.generateBox(size: [0.2,0.2,0.2])], mode: .trigger, filter: .sensor)
            
            collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Began.self){ event in
                
                /// entityA , entityB: box와 floor가 A와 B로 인식
                ///event.entityA.removeFromParent()
                
                box.model?.materials = [SimpleMaterial(color: .purple, isMetallic: true)]
            })
            
            collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Ended.self){ event in
                box.model?.materials = [SimpleMaterial(color: .green, isMetallic: true)]
            })
            
            box.position = simd_make_float3(0,0.7,0)
            
            anchorEntity.addChild(box)
            view.scene.anchors.append(anchorEntity)
            
            
        }
    }
}

 


Collision Detection (2) - CollisionEvent.entityA, CollisionEvent.entityB

class Coordinator: NSObject, ARSessionDelegate {
    
    weak var view: ARView?
    var collisionSubscriptions = [Cancellable]()
    
    func buildEnvironment() {
        guard let view = view else { return }
        let anchor = AnchorEntity(plane: .horizontal)
        
        let box1 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .red, isMetallic: true)])
        box1.generateCollisionShapes(recursive: true)
        box1.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .sensor)
        
        let box2 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .white, isMetallic: true)])
        box2.generateCollisionShapes(recursive: true)
        box2.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .sensor)
        box2.position.z = 0.3
        
        let sphere1 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .green, isMetallic: true)])
        sphere1.generateCollisionShapes(recursive: true)
        sphere1.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .sensor)
        sphere1.position.x += 0.3
        
        let sphere2 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .cyan, isMetallic: true)])
        sphere2.generateCollisionShapes(recursive: true)
        sphere2.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .sensor)
        sphere2.position.x -= 0.3
        
        
        anchor.addChild(box1)
        anchor.addChild(box2)
        anchor.addChild(sphere1)
        anchor.addChild(sphere2)
        
        view.scene.addAnchor(anchor)
        
        view.installGestures(.all, for: box1)
        view.installGestures(.all, for: box2)
        
        collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Began.self ){ event in
            
            guard let entity1 = event.entityA as? ModelEntity,
                  let entity2 = event.entityB as? ModelEntity else { return }
            
            /// entity A와 B가 결정되는 기준과, AB의 역할은 딱히 중요하지 않은건가..?
            entity1.model?.materials = [SimpleMaterial(color: .green, isMetallic: true)]
            entity2.model?.materials = [SimpleMaterial(color: .green, isMetallic: true)]
        })
        
        collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Ended.self ){ event in
            
            guard let entity1 = event.entityA as? ModelEntity,
                  let entity2 = event.entityB as? ModelEntity else { return }
            
            entity1.model?.materials = [SimpleMaterial(color: .red, isMetallic: true)]
            entity2.model?.materials = [SimpleMaterial(color: .red, isMetallic: true)]
        })
    }
}

 


Collision Detection (3) - Collision Filters, CollisionGroup

같은 collisionMask끼리만 충돌할 수 있다!

박스와는 충돌 감지, 구와는 충돌 감지하지 않음

//CollisionComponent (group, mask)
sphere1.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .init(group: sphereGroup, mask: sphereMask))
import Foundation
import ARKit
import RealityKit
import Combine

/// want to get a notification whenever a collision is happening between the two model entities

class Coordinator: NSObject, ARSessionDelegate {
    
    weak var view: ARView?
    var collisionSubscriptions = [Cancellable]()
    
    /// CollisionGroup : Layer 설정
    let boxGroup = CollisionGroup(rawValue: 1 << 0)
    let sphereGroup = CollisionGroup(rawValue: 1 << 1)
    
    func buildEnvironment() {
        guard let view = view else { return }
        
        // create masks : box끼리, sphere끼리 부딪힘
        let boxMask = CollisionGroup.all.subtracting(sphereGroup)
        let sphereMask = CollisionGroup.all.subtracting(boxGroup)
        
        let anchor = AnchorEntity(plane: .horizontal)
        
        let box1 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .red, isMetallic: true)])
        box1.generateCollisionShapes(recursive: true)
        /// mask: different objects that it can actually collide to
        /// if it belongs to the same group and if they ahve the same collision mask, then it means that those particular virtual objects can collide within themselves
        box1.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .init(group: boxGroup, mask: boxMask))
        
        
        let box2 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .white, isMetallic: true)])
        box2.generateCollisionShapes(recursive: true)
        box2.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .init(group: boxGroup, mask: boxMask))
        box2.position.z = 0.3
        
        let sphere1 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .green, isMetallic: true)])
        sphere1.generateCollisionShapes(recursive: true)
        sphere1.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .init(group: sphereGroup, mask: sphereMask))
        sphere1.position.x += 0.3
        
        let sphere2 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .cyan, isMetallic: true)])
        sphere2.generateCollisionShapes(recursive: true)
        sphere2.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .init(group: sphereGroup, mask: sphereMask))
        sphere2.position.x -= 0.3
        
        
        anchor.addChild(box1)
        anchor.addChild(box2)
        anchor.addChild(sphere1)
        anchor.addChild(sphere2)
        
        view.scene.addAnchor(anchor)
        
        view.installGestures(.all, for: box1)
        view.installGestures(.all, for: box2)
        
        collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Began.self ){ event in
            
            guard let entity1 = event.entityA as? ModelEntity,
                  let entity2 = event.entityB as? ModelEntity else { return }
            
            /// entity A와 B가 결정되는 기준과, AB의 역할은 딱히 중요하지 않은건가..?
            entity1.model?.materials = [SimpleMaterial(color: .green, isMetallic: true)]
            entity2.model?.materials = [SimpleMaterial(color: .green, isMetallic: true)]
        })
        
        collisionSubscriptions.append(view.scene.subscribe(to: CollisionEvents.Ended.self ){ event in
            
            guard let entity1 = event.entityA as? ModelEntity,
                  let entity2 = event.entityB as? ModelEntity else { return }
            
            entity1.model?.materials = [SimpleMaterial(color: .red, isMetallic: true)]
            entity2.model?.materials = [SimpleMaterial(color: .red, isMetallic: true)]
        })
    }
}

Collision Detection and Physics

import Foundation
import ARKit
import RealityKit
import Combine

/// want to get a notification whenever a collision is happening between the two model entities

class Coordinator: NSObject, ARSessionDelegate, UIGestureRecognizerDelegate {
    
    weak var view: ARView?
    var collisionSubscriptions = [Cancellable]()
    
    /// CollisionGroup : Layer 설정
    let boxGroup = CollisionGroup(rawValue: 1 << 0)
    let sphereGroup = CollisionGroup(rawValue: 1 << 1)
    
    var movableEntities = [ModelEntity]()
    
    func buildEnvironment() {
        guard let view = view else { return }

        let anchor = AnchorEntity(plane: .horizontal)
        
        // create a floor
        /// plane에 width, height, depth가 있었다니
        let floor = ModelEntity(mesh: .generatePlane(width: 1, depth: 1), materials: [SimpleMaterial(color: .blue, isMetallic: true)])
        floor.generateCollisionShapes(recursive: true)
        floor.physicsBody = PhysicsBodyComponent( massProperties: .default, material: .default, mode: .static)
        
        
        
        let box1 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .red, isMetallic: true)])
        box1.physicsBody = PhysicsBodyComponent( massProperties: .default, material: .default, mode: .dynamic)
        box1.generateCollisionShapes(recursive: true)
        /// mask: different objects that it can actually collide to
        /// if it belongs to the same group and if they ahve the same collision mask, then it means that those particular virtual objects can collide within themselves
        box1.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .sensor)
        box1.position.y = 0.3
        
        
        let box2 = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial(color: .white, isMetallic: true)])
        box2.generateCollisionShapes(recursive: true)
        box2.physicsBody = PhysicsBodyComponent( massProperties: .default, material: .default, mode: .dynamic)
        box2.collision = CollisionComponent(shapes: [.generateBox(size: [0.2, 0.2, 0.2])],mode: .trigger, filter: .sensor)
        box2.position.z = 0.3
        box2.position.y = 0.3
        
        /// filter: .sensor 은 아무거나 충돌
        let sphere1 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .green, isMetallic: true)])
        sphere1.generateCollisionShapes(recursive: true)
        sphere1.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)
        sphere1.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .sensor)
        sphere1.position.x += 0.3
        sphere1.position.y = 0.3
        
        let sphere2 = ModelEntity(mesh: .generateSphere(radius: 0.2), materials: [SimpleMaterial(color: .cyan, isMetallic: true)])
        sphere2.generateCollisionShapes(recursive: true)
        sphere2.physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)
        sphere2.collision = CollisionComponent(shapes: [.generateSphere(radius: 0.2)],mode: .trigger, filter: .sensor)
        sphere2.position.x -= 0.3
        sphere2.position.y = 0.3
        
        anchor.addChild(floor)
        
        anchor.addChild(box1)
        anchor.addChild(box2)
        anchor.addChild(sphere1)
        anchor.addChild(sphere2)
        
        movableEntities.append(box1)
        movableEntities.append(box2)
        movableEntities.append(sphere1)
        movableEntities.append(sphere2)
        
        
        view.scene.addAnchor(anchor)
        
        movableEntities.forEach {
            view.installGestures(.all, for: $0).forEach {
                $0.delegate = self
            }
        }
  
  
        setupGestures()
    }
    
    fileprivate func setupGestures() {
        guard let view = view else { return }
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panned(_:)))
        panGesture.delegate = self
        view.addGestureRecognizer(panGesture)
    }
    
    @objc func panned(_ gesture: UIPanGestureRecognizer) {
        switch gesture.state {
            case .ended, .cancelled, .failed:
            // change the physics mode to dynamic
            movableEntities.compactMap {$0}.forEach {
                $0.physicsBody?.mode = .dynamic
            }
            
            default :
            return
        }
    }
    
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReconizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
    
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        guard let translationGesture = gestureRecognizer as? EntityGestureRecognizer,
              let entity = translationGesture.entity as? ModelEntity else {
            return true
        }
        
        entity.physicsBody?.mode = .kinematic
        return true
        
    }
}

Refactoring

enum Shape {
    case box
    case sphere
}


class MovableEntity: Entity, HasModel, HasPhysics, HasCollision {
    
    var size: Float!
    var color: UIColor!
    var shape: Shape = .box
    
    init(size: Float, color: UIColor, shape: Shape) {
        super.init()
        self.size = size
        self.color = color
        self.shape = shape
        
        let mesh = generateMeshResources()
        let material = generateMaterial()
        
        model = ModelComponent(mesh: mesh, materials: [material])
        physicsBody = PhysicsBodyComponent(massProperties: .default, material: .default, mode: .dynamic)
        collision = CollisionComponent(shapes: [generateShapeResource()], mode:.trigger, filter: .sensor)
        
        generateCollisionShapes(recursive: true)
    }
    
    
    private func generateMaterial() -> Material {
        SimpleMaterial(color:color, isMetallic: true)
    }
    
    private func generateMeshResources() -> MeshResource{
        switch shape {
        case .box:
            return MeshResource.generateBox(size: size)
        case .sphere:
            return MeshResource.generateSphere(radius: size)
        }
    }
    
    private func generateShapeResource() -> ShapeResource {
        switch shape {
        case .box:
            return ShapeResource.generateBox(size: [self.size,self.size,self.size])
        case .sphere:
            return ShapeResource.generateSphere(radius: self.size)
        }
    }
    
    @MainActor @preconcurrency required init() {
        fatalError("init() has not been implemented")
    }
    
}