【visionOS】3Dモデルをドラッグ&ドロップできるようにする方法

本記事では3Dモデルをドラッグ&ドロップできるようにする方法を紹介します。

## デモ

![画面収録 2024-05-13 18.11.22.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1042576/b2382f53-a7fe-d395-e10f-0849cf311e85.gif)

## 実装

以下のコードは、RealityView内で3Dモデルを表示し、モデルをドラッグできるようにする実装です。

```swift
struct ImmersiveView: View {
    var body: some View {
        RealityView { content in
            if let entity = try? await ModelEntity(named: "Ball")
                {
                entity.generateCollisionShapes(recursive: false)
                entity.components.set(InputTargetComponent())
                content.add(entity)
            }
        }
        .gesture(dragGesture)
    }
    
    private var dragGesture: some Gesture {
        DragGesture()
            .targetedToAnyEntity()
            .onChanged { value in
                value.entity.position = value.convert(value.location3D, from: .local, to: value.entity.parent!)
            }
    }
}
```
`DragGesture()` を使用して、エンティティをドラッグできるようにします。`onChanged` クロージャ内でエンティティの位置を更新します。

RealitiyViewでイベントの入力を受け取るには、ModelEntityにInputTargetComponentとCollisionComponentの両方を設定する必要があります。

Reality Composer Proを使用してアセットに直接コンポーネントを指定することもできますが、この例ではコードで設定しています。

WWDCのセッション[Build spatial experiences with RealityKit - WWDC23](https://developer.apple.com/videos/play/wwdc2023/10080/?time=964)では、以下のように説明されています。

![スクリーンショット 2024-05-10 16.33.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1042576/e2a216b2-60e4-01ad-fb62-1175a67f9bfd.png)

> You can add a gesture to a RealityView, like any other SwiftUI view, and it will hit test against entities in that view. To receive input, the entity must have both an input target component and a collision component. When a touch event is handled by RealityView, it will ignore any entities that don't have both collision and an input target. Only this last entity has both components, so gestures added to this RealityView will only react to input directed at this entity.

また、ModelEntityに`PhysicsBodyComponent`と`PhysicsMotionComponent`を設定し、モデルに物理特性とモーション特性を追加することで、ドラッグ&ドロップも可能になります。

![画面収録 2024-05-13 18.12.13 (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1042576/6a849fb4-befb-be4a-baa7-fd60f3eaf7a3.gif)

以下のコードでは、RealityKitを使用して床とボールの3Dモデルを生成し、ユーザーがボールをドラッグ&ドロップできるようにしています。

```swift
struct ImmersiveView: View {

    var body: some View {
        RealityView { content in
            let floor = ModelEntity(mesh: .generatePlane(width: 50, depth: 50), materials: [OcclusionMaterial()])
            floor.generateCollisionShapes(recursive: false)
            floor.components[PhysicsBodyComponent.self] = .init(
                massProperties: .default,
                material: .generate(staticFriction: 0.8, dynamicFriction: 0.7, restitution: 0.2),
                mode: .static
            )
            content.add(floor)

            if let entity = try? await ModelEntity(named: "Ball")
                {
                entity.generateCollisionShapes(recursive: false)
                entity.components.set(InputTargetComponent())
                entity.components[PhysicsBodyComponent.self] = .init(
                    massProperties: .default,
                    material: .generate(staticFriction: 0.6, dynamicFriction: 0.2, restitution: 0.5),
                    mode: .dynamic
                )
                entity.components[PhysicsMotionComponent.self] = .init()
                content.add(entity)
            }
        }
        .gesture(dragGesture)
    }

    private var dragGesture: some Gesture {
        DragGesture()
            .targetedToAnyEntity()
            .onChanged { value in
                value.entity.position = value.convert(value.location3D, from: .local, to: value.entity.parent!)
                value.entity.components[PhysicsBodyComponent.self]?.mode = .kinematic
            }
            .onEnded { value in
                value.entity.components[PhysicsBodyComponent.self]?.mode = .dynamic
            }
    }
}
```

床とボールのモデルには、PhysicsBodyComponentとPhysicsMotionComponentを設定することで、物理特性とモーション特性を追加しています。

https://developer.apple.com/documentation/realitykit/physicsbodycomponent/init(massproperties:material:mode:)

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


## 参考資料

https://1planet.co.jp/tech-blog/applevisionpro-realityview-model-drag

https://youtu.be/Ihjfl_6tkKw?si=kN_orMpUcDUGn1rs