Machineboy空

RealityKit - CollisionGroup과 Collision Filter를 알아보자! 본문

언어/iOS

RealityKit - CollisionGroup과 Collision Filter를 알아보자!

안녕도라 2024. 11. 18. 22:26

오늘의 구현

모드에 따라 제스처가 가능한 레이어를 구분하고 싶다!

즉, 큐브모드에서는 큐브에만 제스처가 적용이 되고, 구를 눌러도 아무런 제스처를 취할 수 없으며

구 모드에서는 구에만 제스처가 적용이 되게 하고 싶다.

arView.installGestures([.all], for: cubeModel)
arView.installGestures([], for: cubeModel)

 

단순히 installGestures에 빈배열을 할당하는 것으로 제스처가 해제되지 않더라.

 

Unity의 LayerMask와 같은 개념이 RealityKit의 CollisionFilter인듯한데 한번 살펴보자!


CollisionFilter

A set of masks that determine whether entities can collide during simulations
실행 중에 엔티티가 서로 충돌할 수 있는지 결정하는 mask.

Use Collision filters in combination with collision groups to define which entities collide with which other entities in a scene.
collision Group과 함께 쓰여 서로 충돌할 수 있는 그룹인지 결정한다.

 

Group과 mask

  • group: 엔티티가 속한 그룹
  • mask: 엔티티가 충돌할 수 있는 그룹, 어떤 객체와 충돌할지 정의
//예시 1. A는 C와 충돌하지만 B와는 충돌하지 않는다.

let groupA = CollisionGroup(rawValue: 1 << 0)
let groupB = CollisionGroup(rawValue: 1 << 1)
let groupC = CollisionGroup(rawValue: 1 << 2)

let theFilter = CollisionFilter(group: groupA, mask: groupA.union(groupC))
// 예시 2. 모든 그룹과 충돌하되 groupB와는 충돌하지 않는다

let notGroupB = CollisionGroup.all.subtracting(groupB)

 

https://developer.apple.com/documentation/realitykit/collisionfilter

 

CollisionFilter | Apple Developer Documentation

A set of masks that determine whether entities can collide during simulations.

developer.apple.com


CollisionGroup

You use collision groups along with `CollisionFilter` to define custom collision properties for entities in your scene and controlling which entities collide wit which other entities.
CollisionGroup은 CollisionFilter와 함께 사용되어, 장면 내 엔티티들 간의 충돌 규칙을 정의할 수 있다. 이를 통해 어떤 엔티티가 다른 엔티티와 충돌할지 제어할 수 있다.
// 예시: Red팀 player가 같은 red팀과는 충돌하지 않게 하기 위한 설정

let redGroup = CollisionGroup(rawValue: 1<< 0)
let allButRedGroup = CollisionGroup.all.subtracting(redGroup)
let allButRedFilter = CollisionFilter(group: redGroup, mask: allButRedGroup)

redTeamPlayer1.collision?.filter = allButRedFilter

 

https://developer.apple.com/documentation/realitykit/collisiongroup

 

CollisionGroup | Apple Developer Documentation

A bitmask used to define the collision group to which an entity belongs.

developer.apple.com

// sphereModel은 sphereGroup이라는 CollisionGroup에 속하고, 모든 것과 충돌 가능하다.
sphereModel.collision?.filter = CollisionFilter(group: sphereGroup, mask: .all)

// cubeModel은 cubeGroup이라는 CollisionGroup에 속하고, 그 아무 것도 충돌 불가능하다.
cubeModel.collision?.filter = CollisionFilter(group: boxGroup, mask: [])

내가 만든 샘플코드

import SwiftUI
import ARKit
import RealityKit

// CollisionGroup
let boxGroup = CollisionGroup(rawValue: 1 << 0)
let sphereGroup = CollisionGroup(rawValue: 1 << 1)

struct CollisionFilterTest: View {
    @State var currentItem: String = "cube" 
    
    var body: some View {
        ZStack {
            ARCollisionViewContainer(currentItem: $currentItem)
                .ignoresSafeArea()
            
            VStack {
                Spacer()
                HStack(spacing: 30) {
                    Button(action: {
                        currentItem = "cube"
                    }) {
                        Image(systemName: "cube.fill")
                            .resizable()
                            .frame(width: 70, height: 70)
                            .foregroundColor(currentItem == "cube" ? .orange : .gray)
                    }
                    Button(action: {
                        currentItem = "sphere"
                    }) {
                        Image(systemName: "tennisball.fill")
                            .resizable()
                            .frame(width: 70, height: 70)
                            .foregroundColor(currentItem == "sphere" ? .blue : .gray)
                    }
                }
                .padding()
            }
        }
    }
}

struct ARCollisionViewContainer: UIViewRepresentable {
    @Binding var currentItem: String 
    
    func makeUIView(context: Context) -> ARView {
        let arView = ARView(frame: .zero, cameraMode: .nonAR, automaticallyConfigureSession: false)
        arView.environment.background = .color(.white)
        
        // MARK: Cube
        let cubeMaterial = SimpleMaterial(color: .orange, isMetallic: false)
        let cubeMesh = MeshResource.generateBox(size: 1)
        let cubeModel = ModelEntity(mesh: cubeMesh, materials: [cubeMaterial])
        cubeModel.generateCollisionShapes(recursive: true)
        
        let cubeAnchor = AnchorEntity(world: [-1, 0, 0])
        cubeAnchor.addChild(cubeModel)
        arView.scene.anchors.append(cubeAnchor)
        
        // MARK: Sphere
        let sphereMaterial = SimpleMaterial(color: .blue, isMetallic: false)
        let sphereMesh = MeshResource.generateSphere(radius: 0.5)
        let sphereModel = ModelEntity(mesh: sphereMesh, materials: [sphereMaterial])
        sphereModel.generateCollisionShapes(recursive: true)
        
        let sphereAnchor = AnchorEntity(world: [1, 0, 0])
        sphereAnchor.addChild(sphereModel)
        arView.scene.anchors.append(sphereAnchor)
        
        context.coordinator.cubeModel = cubeModel
        context.coordinator.sphereModel = sphereModel
        
        // MARK: Camera
        let camera = PerspectiveCamera()
        camera.position = [0, 7, 7]
        camera.look(at: [0, 0, 0], from: camera.position, relativeTo: nil)
        
        let cameraAnchor = AnchorEntity(world: [0, 0, 0])
        cameraAnchor.addChild(camera)
        arView.scene.addAnchor(cameraAnchor)
        
        // Install initial gestures
        arView.installGestures([.all], for: cubeModel)
        arView.installGestures([], for: sphereModel)
        
        context.coordinator.arView = arView
        
        return arView
    }
    
    func updateUIView(_ arView: ARView, context: Context) {
        context.coordinator.updateGestures(for: currentItem)
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    class Coordinator {
        var cubeModel: ModelEntity?
        var sphereModel: ModelEntity?
        var arView: ARView?
        
        func updateGestures(for currentItem: String) {
            guard let arView = arView, let cubeModel = cubeModel, let sphereModel = sphereModel else { return }
            
            switch currentItem {
            case "cube":
                arView.installGestures([.all], for: cubeModel)
                cubeModel.collision?.filter = CollisionFilter(group: boxGroup, mask: .all)
                sphereModel.collision?.filter = CollisionFilter(group: sphereGroup, mask: [])
            case "sphere":
                arView.installGestures([.all], for: sphereModel)
                sphereModel.collision?.filter = CollisionFilter(group: sphereGroup, mask: .all)
                cubeModel.collision?.filter = CollisionFilter(group: boxGroup, mask: [])
            default:
                break
            }
        }
    }
}

#Preview {
    CollisionFilterTest()
}