RealityKit の参考書

#まえがき
iOSでARアプリを作るならRealityKit。
この記事では基本から応用まで、RealityKitの使い方を知ることができます。
入門に、索引に、ご利用ください。

(もしもiOSアプリを作ったことがない場合は、「[最初のアプリを作る](https://qiita.com/john-rocky/items/204e7015bae18c0f78fa)」をよんでください。)


#もくじ
###### [1.AR画面を表示する ](#1ar画面を表示する)

|[<img src="https://user-images.githubusercontent.com/23278992/136671144-a7650be1-3a65-4578-9c70-170a63678f14.PNG" width="150">](#1ar画面を表示する)|
|---|
|[AR画面](#1ar画面を表示する)|
###### [2.オブジェクトを表示する ](#2オブジェクトを表示する)

|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/54939280-c105-9bc0-f694-d8e66b3ff5c2.png" width="150">](#ボックス)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/631a83fa-0b2e-3223-053d-6a7ad6cdc2f8.png" width="150">](#板)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/ffb09c8f-eb72-c3fc-44f9-3f2f98aa1220.png" width="150">](#球)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/7d53bcbf-149e-9c38-8648-6dab754834f8.png" width="150">](#テキスト)|
|---|---|---|---|
|[ボックス](#ボックス)|[板](#板)|[球](#球)|[テキスト](#テキスト)|
|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/de12d793-bf2e-0341-c500-95c5eeeefca3.png" width="150">](#usdzモデルを表示する)|
|[USDZモデル](#usdzモデルを表示する)|

###### [3.表面を加工する ](#3表面を加工する)

|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/996617ba-67a6-5069-3a70-fe314a6d8893.png" width="150">](#シンプルマテリアル)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/e55c7f24-767f-2d12-7b9d-9f6581ffeedf.png" width="150">](#アンリットマテリアル)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/39f637a1-b2e6-40e2-fa4a-311f627f60e5.png" width="150">](#オクルージョンマテリアル)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/e95b6a27-0efa-d8cc-b2a4-5a255715016d.jpeg" width="150">](#画像テクスチャ)|
|---|---|---|---|
|[シンプル](#シンプルマテリアル)|[アンリット](#アンリットマテリアル)|[オクルージョン](#オクルージョンマテリアル)|[画像](#画像テクスチャ)|
|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/95e65d42-4ae9-fba9-4897-a74aea0d008f.gif" width="150">](#動画マテリアル)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/b154bf26-e737-10bc-92db-3fe6edcc0c71.png" width="150">](#usdzの表面を加工)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/a9e25ac1-d29d-3a8e-eaa1-f58fdb5d0557.gif" width="150">](#usdz動画)||
|[動画](#動画マテリアル)|[USDZ](#usdzの表面を加工)|[USDZ+動画](#usdz動画)||


###### [4.位置や角度を調整する ](#4オブジェクトの位置や角度を調整する)

###### [5.現実のアンカー(目印)をつかって配置する ](#5現実のアンカー目印をつかって配置する)

|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/fd225e11-6213-2b2b-5805-418c656f1610.png" width="150">](#水平面アンカー)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/dde1cb66-6220-d992-562a-065880f3f5f2.png" width="150">](#垂直面アンカー)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/3dbdb43e-c60b-aebe-5de3-6c0ea4835640.jpeg" width="150">](#画像アンカー)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/3eb555e0-a2b9-6e8a-9456-57c334f01b8d.png" width="150">](#オブジェクトアンカー)|
|---|---|---|---|
|[水平面](#水平面アンカー)|[垂直面](#垂直面アンカー)|[画像](#画像アンカー)|[オブジェクト](#オブジェクトアンカー)|
|[<img src="https://user-images.githubusercontent.com/23278992/121797476-50864880-cc5b-11eb-8e67-8269b0f1cb14.gif" width="150">](#顔アンカー)|[|[🐣](#ARアンカー)|[<img src="" width="150">](#)|
|[顔](#顔アンカー)|[からだ](#からだアンカ-)|[AR](#ARアンカー)|[](#)|

(画像アンカー・デモ)
[<img src="https://user-images.githubusercontent.com/23278992/121764235-6e7d7b80-cb7d-11eb-8c44-128515809946.gif" width="450">](#)

###### [6.アニメーション ](#6アニメーション)

|[<img src="https://user-images.githubusercontent.com/23278992/121795076-3b53ee80-cc48-11eb-9a5f-f4591c46a508.gif" width="150">](#移動)|[<img src="https://user-images.githubusercontent.com/23278992/121795146-d3ea6e80-cc48-11eb-8552-cc0214278cc4.gif" width="150">](#回転)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/967443e1-8d0a-886b-dfaa-917b47ed1448.gif" width="150">](#拡大縮小)|[<img src="https://user-images.githubusercontent.com/23278992/121807789-9e697380-cc90-11eb-97fe-ff2227f9a18a.gif" width="150">](#usdzアニメーション)|
|---|---|---|---|
|[移動](#移動)|[回転](#回転)|[拡大・縮小](#拡大縮小)|[USDZ](#usdzアニメーション)|

###### [7.照明 ](#7照明)

|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/4e42471a-5b32-5ca9-50aa-f435dbf7fcb6.png" width="150">](#ポイントライト)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/16c4f6ef-c802-c3d3-40af-55d26b7c81bb.png" width="150">](#ダイレクショナルライト)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/a57fd2da-cd85-5f1b-d6ee-7056fdfc3a66.png" width="150">](#スポットライト)|
|---|---|---|
|[ポイント](#ポイントライト)|[ダイレクショナル](#ダイレクショナルライト)|[スポット](#スポットライト)|

###### [8.物理作用 ](#8物理作用)

|[<img src="https://user-images.githubusercontent.com/23278992/121936871-08634500-cd85-11eb-8fd8-1660466a861a.gif" width="150">](#9物理作用)|[<img src="https://user-images.githubusercontent.com/23278992/122688018-b58dff80-d254-11eb-8b94-28fca4016117.gif" width="150">](#衝突検知)|
|---|---|
|[物理・衝突](#8物理作用)|[衝突検知](#衝突検知)|

###### [9.よりリアルな現実世界との相互作用 ](#9よりリアルな現実世界との相互作用)

###### [10.オーディオ ](#10オーディオ)
###### [11.タップとジェスチャー ](#11タップとジェスチャー)

|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/63c1651f-b1ee-3e1f-78df-6836a3e939d6.gif" width="150">](#タップ)|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/50fa6cee-f471-852a-098b-6f59fc091ab3.gif" width="150">](#ジェスチャーで移動回転拡大縮小)|
|---|---|
|[タップ](#タップ)|[ジェスチャー](#ジェスチャーで移動回転拡大縮小)|

###### [12.カスタム・コンポーネント ](#12カスタム・コンポーネント)
###### [13.複数のデバイスで共有する ](#13複数のデバイスで共有する)

|[<img src="https://user-images.githubusercontent.com/23278992/122625715-8014d500-d0e1-11eb-8e24-9c10a73762b8.gif" width="150">](#14複数のデバイスで共有する)|[<img src="https://user-images.githubusercontent.com/23278992/122658198-689f2000-d1a5-11eb-9ad4-4f490fc67be5.gif" width="150">](#ジェスチャーで移動回転拡大縮小)|
|---|---|
|[デバイス間共有](#14複数のデバイスで共有する)|[#デバイス・アンカー](#他のデバイスの位置アンカー)|

###### [14.RealityComposerで手軽にシーンをつくる ](#14realitycomposerで手軽にシーンをつくる)

|[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/c106f211-bb21-6845-d386-b8f3ee4fb676.gif" width="150">](#15realitycomposerで手軽にシーンをつくる)[<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/3f1dd232-c6d9-d101-a7aa-c0d3b2c8c6bc.gif" width="150">](#14realitycomposerで手軽にシーンをつくる)|
|---|
|[RealityComposer](#14realitycomposerで手軽にシーンをつくる)|

###### [15.イベントを検知する  ](#15イベントを検知する)

|[<img src="https://user-images.githubusercontent.com/23278992/124534020-61873b80-de4e-11eb-906b-1919cc8e06bf.gif" width="150">](#15イベントを検知する)|
|---|
|[イベント検知](#15イベントを検知する)|

###### [16.ARKitとRealityKitの関係と注意点 ](#16arkitとrealitykitの関係と注意点)
###### [17.RealityKitをつかうメリット ](#17realitykitをつかうメリット)
###### [18.RealityKitのサンプルコード集 ](#18realitykitのサンプルコード集)

![180.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/5ef6cd2f-888c-f7b3-627b-326777fb6a03.png)

|[<img src="https://user-images.githubusercontent.com/23278992/122830079-cc028c80-d322-11eb-87a3-8aa4803860a4.png" width="150">](https://github.com/john-rocky/RealityKit-Sampler)|[<img src="https://user-images.githubusercontent.com/23278992/124874290-83292400-e002-11eb-948f-13302a5806ed.gif" width="150">](https://github.com/john-rocky/RealityKit-Sampler)|[<img src="https://user-images.githubusercontent.com/23278992/123641992-c8f52800-d85d-11eb-918a-4619071a54c9.gif" width="150">](https://github.com/john-rocky/RealityKit-Sampler)|[<img src="https://user-images.githubusercontent.com/23278992/123644152-11ade080-d860-11eb-828f-68e86ac8ee28.gif" width="150">](https://github.com/john-rocky/RealityKit-Sampler)|
|---|---|---|---|
|[Put A Box](https://github.com/john-rocky/RealityKit-Sampler)|[Giant Robot](https://github.com/john-rocky/RealityKit-Sampler)|[100inch-Monitor](https://github.com/john-rocky/RealityKit-Sampler)|[Building blocks](https://github.com/john-rocky/RealityKit-Sampler)|
||(https://github.com/john-rocky/RealityKit-Sampler)|[<img src="https://user-images.githubusercontent.com/23278992/124336683-e158a000-dbd9-11eb-8a00-5c2943daefcb.gif" width="150">](https://github.com/john-rocky/RealityKit-Sampler)|[<img src="" width="150">](https://github.com/john-rocky/RealityKit-Sampler)|
|[Speech Balloon](https://github.com/john-rocky/RealityKit-Sampler)|[Special Move](https://github.com/john-rocky/RealityKit-Sampler)|[Face Cropper](https://github.com/john-rocky/RealityKit-Sampler)|[AR Hockey](https://github.com/john-rocky/RealityKit-Sampler)|
|[<img src="https://user-images.githubusercontent.com/23278992/125152071-59e4d100-e185-11eb-8f4c-e14a825ada09.gif" width="150">](https://github.com/john-rocky/RealityKit-Sampler)||||
|[Hand Interaction](https://github.com/john-rocky/RealityKit-Sampler)||||

# [1.AR画面を表示する](#もくじ)

<img src="https://user-images.githubusercontent.com/23278992/136671144-a7650be1-3a65-4578-9c70-170a63678f14.PNG" width="200">

ARViewを置くことで、AR表示可能なカメラ画面が表示されます。

【実装方法】

**※Info.PlistでCamera Usage Descriptionを追加**

### UIKit

```swift
import UIKit
import RealityKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let arView = ARView(frame: view.bounds)
        view.addSubView(arView)
        // ストーリーボードの@IBOutlet接続でも大丈夫です
    }
}
```

### SwiftUI

```swift
import SwiftUI
import RealityKit

struct ContentView : View {
    var body: some View {
        return ARViewContainer()
            .edgesIgnoringSafeArea(.all) // 全画面表示
    }
}

struct ARViewContainer: UIViewRepresentable {
    
    func makeUIView(context: Context) -> ARView {
        
        let arView = ARView(frame: .zero)

        // ARView が読み込まれるときにしたい処理をかく書く
                
        return arView
        
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {}
    
}

```

### ARViewのサブクラス化

SwiftUIでは、ARViewをUIViewRepresentableで表示するか、もしくはARViewを持つViewControllerを　UIViewControllerRepresentableで表示すると便利です。
このようにARViewをサブクラス化することで、ARに関するコードを他から分離して書きやすくなります。

##### UIViewRepresentable

ARViewのサブクラスをViewとして表示する方法

```swift:ContentView.swift
import SwiftUI
import RealityKit

struct ContentView: View {    
    var body: some View {
        ARViewContainer()
            .edgesIgnoringSafeArea(.all) // 全画面表示
    }
}

struct ARViewContainer: UIViewRepresentable {
    
    func makeUIView(context: Context) -> ARView {
        let customARView = CustomARView(frame: .zero)
         // ゼロの CGRect で初期化することで、 View の幅いっぱいの表示になります
        
        return customARView
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {

    }
}
```

```swift:CustomARView.swift
import UIKit
import RealityKit

class CustomARView: ARView {
    required init(frame frameRect: CGRect) {
        super.init(frame: frameRect)
    }
    
    required init?(coder decoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
```

##### UIViewControllerRepresentable

ARViewControllerごとViewとして表示する方法

```swift:ContentView.swift
import SwiftUI
import RealityKit

struct ContentView: View {    
    var body: some View {
        ARViewControllerContainer()
            .edgesIgnoringSafeArea(.all) // 全画面表示
    }
}

struct ARViewControllerContainer: UIViewControllerRepresentable {
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<ARViewControllerContainer>) -> UIViewController {
        let viewController = ARViewController()
        return viewController
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<ARViewControllerContainer>) {
    }
    
    func makeCoordinator() -> ARViewControllerContainer.Coordinator {
        return Coordinator()
    }
    
    class Coordinator {
        
    }
}
```

```swift:ARViewController
import UIKit
import RealityKit

class ARViewController: UIViewController {
    
    private var arView: ARView!

    override func viewDidLoad() {
        super.viewDidLoad()
        arView = ARView(frame: view.bounds)
        view.addSubview(arView)
    }

}
```

# [2.オブジェクトを表示する](#もくじ)

RealityKitのシンプルなオブジェクト、もしくはUSDZを表示できます。

### ボックス

【ボックスメッシュ】

![IMG_5099のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/54939280-c105-9bc0-f694-d8e66b3ff5c2.png)
【実装方法】

```swift
let anchor = AnchorEntity() // アンカー（ARモデルを固定する錨）
anchor.position = simd_make_float3(0, -0.5, -1) // アンカーの位置は、デバイス初期位置から、0.5m下、1m向こう

let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.3, 0.1, 0.2), cornerRadius: 0.03))
 // 幅0.3m、高さ0.1m、奥行き0.2m、角の丸みの半径が0.03mのボックスメッシュからモデルをつくる。　
box.transform = Transform(pitch: 0, yaw: 1, roll: 0) // ボックスモデルをY軸で１ラジアン回転させる

anchor.addChild(box) // アンカーの子階層にボックスを加える
arView.scene.anchors.append(anchor) // arViewにアンカーを加える
```


### 板
【プレイン・メッシュ】

![IMG_5100.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/631a83fa-0b2e-3223-053d-6a7ad6cdc2f8.png)
【実装方法】

```swift
let anchor = AnchorEntity(world: [0, -0.5, -1])

let plane = ModelEntity(mesh: .generatePlane(width: 0.2, height: 0.3))
 // 幅0.2m、高さ0.3mの板メッシュからモデルをつくる。
plane.transform = Transform(pitch: 0, yaw: 1, roll: 0)

anchor.addChild(plane)
arView.scene.anchors.append(anchor)
```


### 球体
【球体メッシュ】

![IMG_5101.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/ffb09c8f-eb72-c3fc-44f9-3f2f98aa1220.png)
【実装方法】

```swift
let anchor = AnchorEntity()
anchor.position = simd_make_float3(0, -0.5, -1)

let sphere = ModelEntity(mesh: .generateSphere(radius: 0.1))
 // 半径0.1mの球体メッシュからモデルをつくる。

anchor.addChild(sphere)
arView.scene.anchors.append(anchor)
```


### テキスト
【テキスト・メッシュ】

![IMG_5102.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/7d53bcbf-149e-9c38-8648-6dab754834f8.png)
【実装方法】

```swift
let anchor = AnchorEntity()
anchor.position = simd_make_float3(0, -0.5, -1)

let text = ModelEntity(mesh: .generateText("Ciao!", extrusionDepth: 0.03, font: .systemFont(ofSize: 0.1, weight: .bold), containerFrame: CGRect.zero, alignment: .center, lineBreakMode: .byCharWrapping))
 // "Ciao!"という文字列、奥行き0.03m、システムフォント0.1m-bold、テキストの折り返しbyCharWrappingでモデルを作成。
 // containerFrameをゼロにすると、その文字列の表示に必要な大きさに文字列のコンテナが調整される。
 // コンテナフレームはモデルの原点（中心）にoriginが設定されるので、origin0だと少し右寄りになる。

 // フォントサイズは、m単位になるので、20にすると20mで表示されてしまうので注意。 
text.transform = Transform(pitch: 0, yaw: 0.3, roll: 0)
        
anchor.addChild(text)
arView.scene.anchors.append(anchor)
```

### [USDZモデルを表示する](#もくじ)

3D の複雑な形状を表示できます。

【恐竜のUSDZ】
![IMG_5110のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/de12d793-bf2e-0341-c500-95c5eeeefca3.png)
【実装方法】

```swift
let anchor = AnchorEntity()
anchor.position = simd_make_float3(0, -0.5, -1)

if let usdzModel = try? Entity.load(named: "raptor") {
    anchor.addChild(usdzModel)
}

arView.scene.anchors.append(anchor)
```

### 非同期ロード

大量の高品質モデルのロードはアプリをブロックします。
非同期ロードが使えます。

```swift
_ = Entity.loadAsync(named: "model1")
    .append(Entity.loadAsync(named: "model2"))
    .append(Entity.loadAsync(named: "model3"))
    .collect()
    .sink { models in 
            // ロード完了時の処理
          }
```

# [3.表面を加工する](#もくじ)

オブジェクトの表面の色・質感・模様をつけます。

### シンプル・マテリアル

反射など現実の光に影響されるマテリアル

【ブルー・メタリックのシンプル・マテリアル】

![IMG_5107のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/996617ba-67a6-5069-3a70-fe314a6d8893.png)

【実装方法】

```swift
let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.3, 0.1, 0.2)))
let material = SimpleMaterial(color: .blue,
                              roughness: 0, // 表面の粗さ
                              isMetallic: true) // 光を反射するか
 // 青色、粗さ0、メタリックのシンプルなマテリアルをつくる

material.metallic = 1 // 光を反射する度合い。 最大値１に近づくと金属的な表面になる

box.model?.materials = [material] // ボックスの表面に適用する
```


### アンリット・マテリアル

物理レンダリングの影響を受けないマテリアル。明るさに影響されないので、暗い場所でもはっきり見える。

【ブルーのアンリット・マテリアル】

![IMG_5397のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/e55c7f24-767f-2d12-7b9d-9f6581ffeedf.png)

【実装方法】

```swift
let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.03, 0.01, 0.02)))
let unlitMaterial = UnlitMaterial(color: .blue)
box.model?.materials = [unlitMaterial]
```

### オクルージョン・マテリアル

ARオブジェクトを透過するマテリアル。

【ロボットの手前にオクルージョン・ボックスを配置】

![IMG_5394のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/39f637a1-b2e6-40e2-fa4a-311f627f60e5.png)

【実装方法】

```swift
let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.03, 0.01, 0.02)))
let occlusionMaterial = OcclusionMaterial()
box.model?.materials = [occlusionMaterial]
```

### 画像テクスチャ

【画像をボックスに貼り付け】
![IMG_5617のコピー.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/e95b6a27-0efa-d8cc-b2a4-5a255715016d.jpeg)

【実装方法】

```swift
let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.5, 0.5, 0.2)))

if let texture = try? TextureResource.load(named: "dolphin") {　// テクスチャ・リソースとして画像を読み込む
    var imageMaterial = UnlitMaterial()
    imageMaterial.baseColor = MaterialColorParameter.texture(texture)　// アンリット・マテリアルのテクスチャに設定する 
    box.model?.materials = [imageMaterial]　// ボックスのマテリアルに設定する
}
```

**【Tips1 画像の明るさのバグ】**
上記のように画像をXcode のアセット・フォルダ（ Assets.xcassets ）から画像名で読み込むと、**オリジナルの画像よりも暗くなるバグが発生することがあります**。
その場合、同じ画像をURLで読み込むと、改善されます。
アセット・フォルダではなく、バンドルのファイルの階層に画像を置き、 URLで読み込みます。

```swift
if let url = Bundle.main.url(forResource: "dolphin", withExtension: "png"),　// メインバンドルの画像URLを取得 
   let texture = try? TextureResource.load(contentsOf:url, withName:nil) {　// 画像URLからテクスチャリソースとして読み込む

   var imageMaterial = UnlitMaterial()
   imageMaterial.baseColor = MaterialColorParameter.texture(texture)
   box.model?.materials = [imageMaterial]

}
```

**【Tips2 UIImageやリモート画像の適用】**
UIImage やリモート URL は直接 TextureResource で読み込めないので、一度ローカルに保存してから URL で読み込みます。

```swift
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("temp.png")

if let uiImage = UIImage(named: "dolphin"),
   let pngData = uiImage.pngData(),
   ((try? pngData.write(to: url)) != nil), // 一度ローカルに書き込む
   let texture = try? TextureResource.load(contentsOf: url) { // 保存した画像のローカルURLからテクスチャリソースとして読み込む
   var imageMaterial = UnlitMaterial()
   imageMaterial.baseColor = MaterialColorParameter.texture(texture)
   box.model?.materials = [imageMaterial]
}
```

**【Tips3 画像の縦横比】**
長方形などでは、メッシュの縦横サイズによって画像の縦横比がゆがみます。
ボックスの横幅を基準として、画像の縦横比にあわせてサイズを決定するには以下のようにできます。
長方形ボックスの場合は側面のセットのどちらかか、上下面のいずれかのアスペクトを諦める必要があります。
以下のコードは上下面を諦めています。

```swift
func getBoxSizeForImage(image: UIImage, boxWidth: Float) -> SIMD3<Float> {
    let imageSize = image.size
    if imageSize.width > imageSize.height {
        let aspect = imageSize.width / imageSize.height
        return [Float(aspect) * boxWidth, boxWidth, boxWidth]
    } else {
        let aspect = imageSize.height / imageSize.width
        return [boxWidth, Float(aspect) * boxWidth, boxWidth]
    }
}
```

```swift
let boxSize = getBoxSizeForImage(image: uiimage!, boxWidth: 0.1) // ボックスの横幅0.1mに合わせて、画像が歪まない縦幅と奥行きを取得する
let box = ModelEntity(mesh: .generateBox(size: boxSize))
```
**【Tips4 Reality Composerのバグ】**
トリビアですが、 Reality Composerで作成したメッシュにコードからテクスチャを貼ろうとすると、画像の向きが逆になったり、画像の一部分しか貼れなかったりします。

### 動画マテリアル

【ボックス・メッシュに動画を貼り付け】
![Jun-09-2021 11-18-40.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/95e65d42-4ae9-fba9-4897-a74aea0d008f.gif)
【実装方法】

＊メインバンドルに動画ファイルを置き、CopyBundleResouseで動画ファイルをリソースとしてリンクしておきます。

```swift
import AVFoundation // 動画再生用にAVFoundationをインポート
```

```swift
let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.5, 0.5, 0.2))) // ボックスを作る

if let videoURL = Bundle.main.url(forResource: "rat", withExtension: "mp4") { // バンドルのmp4ファイルのURLを取得
 // バンドルに動画ファイルを入れる場合は、Copy Bundle Resources に動画ファイルを設定しないと nil が返ってくるので注意
    let asset = AVURLAsset(url: videoURL) // URLからAVAssetを作る
    let playerItem = AVPlayerItem(asset: asset) // AVAssetでAVPlayerItemを作る
    let player = AVPlayer(playerItem: playerItem) // AVPlayerItemでAVPlayerを初期化
    let videoMaterial = VideoMaterial(avPlayer: player) // AVPlayer から VideoMaterial をつくる
    box.model?.materials = [videoMaterial] // ボックスのマテリアルに適用
    player.play() // AVPlayerを再生する
}
```

**【Tips 動画の縦横比】**
動画のアスペクトに合わせて、横幅基準でメッシュのサイズを取得するには、以下のような方法が使えます。

```swift
func getSizeForVideo(baseWidth: Float) -> (Float, Float) {
    guard let url = model.videoURL else { return (baseWidth, baseWidth) }
    let resolution = resolutionForVideo(url: url)
    let width = resolution.0!.width
    let height = resolution.0!.height
        
    guard resolution.1!.b == 0 else {
        if width > height {
            let aspect = Float(width / height)
            return (baseWidth, Float(aspect) * baseWidth)
        } else {
            let aspect = Float(height / width )
            return (Float(aspect) * baseWidth, baseWidth)
        }
    }
        
    if width > height {
        let aspect = Float(width / height)
        return (Float(aspect) * baseWidth, baseWidth)
    } else {
        let aspect = Float(height / width )
        return (0.05, Float(aspect) * baseWidth)
    }
}
    
private func resolutionForVideo(url: URL) -> (CGSize?,CGAffineTransform?) {
    guard let track = AVURLAsset(url: url).tracks(withMediaType: AVMediaType.video).first else { return (nil,nil) }
    let size = track.naturalSize.applying(track.preferredTransform)
    return (CGSize(width: abs(size.width), height: abs(size.height)),track.preferredTransform)
}
```

### USDZの表面を加工

【USDZに青色のシンプル・マテリアルを貼り付け】
![IMG_5131.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/b154bf26-e737-10bc-92db-3fe6edcc0c71.png)
【実装方法】

```swift
let usdzModel = try! Entity.loadModel(named: "raptor") // USDZを読み込む
for index in 0 ..< usdzModel.model!.mesh.expectedMaterialCount { // USDZのマテリアルの数だけ貼り付ける
    let material = SimpleMaterial(color: .blue, roughness: 0, isMetallic: true)
    usdzModel.model?.materials[index] = material
}
```
### USDZ+動画

【USDZに動画マテリアルを貼り付け】
![Jun-11-2021 13-15-45.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/a9e25ac1-d29d-3a8e-eaa1-f58fdb5d0557.gif)
【実装方法】

```swift
let usdzModel = try! Entity.loadModel(named: "raptor")
var videoMaterials:[VideoMaterial] = []
guard let videoURL = Bundle.main.url(forResource: "fire", withExtension: "mp4") else {return}
let asset = AVURLAsset(url: videoURL)
let playerItem = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: playerItem)
let videoMaterial = VideoMaterial(avPlayer: player)

for _ in 0 ..< usdzModel.model!.mesh.expectedMaterialCount { // USDZのマテリアルの数だけ動画マテリアルを作る
    videoMaterials.append(videoMaterial)
}
usdzModel.model?.materials = videoMaterials // マテリアルを適用
player.play()
```

# [4.オブジェクトの位置や角度を調整する](#もくじ)

### 位置

##### ローカル座標の中の位置を設定（原点は親エンティティの原点[0,0,0]）

```swift
entity.position = [-1,1,-2]
 // 親エンティティの原点から１m左、１m上、２m向こうに配置
```
【ローカル座標】

![スクリーンショット 2021-06-19 12.19.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/6c46ae66-cf07-3cbe-8acd-932bfc474386.png)
＊ローカル座標の原点はワールド座標の原点とは異なる（親エンティティがルートシーンの場合は一致する）

##### ワールド座標の中の位置を設定（アプリ起動時のカメラ位置が原点）

```swift
let worldOriginAnchor = AnchorEntity(world: [0,0,0])
arView.scene.addAnchor(worldOriginAnchor)

entity.position = worldOriginAnchor.convert(position: [-1,1,-2], to: entity)
 // ワールド座標の原点（デバイスの初期位置）から１m左、１m上、２m向こうに配置
```

【ワールド座標】
![スクリーンショット 2021-06-19 12.09.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/2ce955f3-120d-15f7-026c-0762c2c74103.png)


### 角度

```swift
let degree:Float = 40 * 180 / .pi
entity.orientation = simd_quatf(angle: degree, axis: [1,0,0])
 // x軸で40度回転
```

### スケール

```swift
entity.setScale([0.5,0.5,0.5], relativeTo: entity)
 // 半分のスケールに

entity.scale = [0.5, 0.5, 0.5]
 // 親エンティティに対してのスケールを半分に
```

### SIMD3<Float>
座標やスケールはSIMD3で指定します。
simd_make_float3 ( ) もしくは配列で初期化できます。

```swift
let xyz:SIMD3<Float> = simd_make_float3(0.3, 0.1, 0.2) 
let xyz:SIMD3<Float> = [0.3, 0.1, 0.2]
 // どちらでも同じ
 // 単位はm
```

# [5.現実のアンカー(目印)をつかって配置する](#もくじ)

現実の特徴点にARを配置できます。
カメラが動いても、オブジェクトはアンカーにとどまります。
アンカーが動くと、オブジェクトは追従します。
アンカーを検出しだい、モデル・エンティティが出現します。

### 水平面アンカー

机や床などをアンカーにできます。

【水平面アンカーで机を検出してボックスを配置】
![IMG_5135.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/fd225e11-6213-2b2b-5805-418c656f1610.png)
【実装方法】

平面アンカーを使用する場合は、 ARWorldTrackingConfiguration で AR セッションを実行します。

```swift
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal, .vertical]
arView.session.run(config, options: [])
```

```swift
let anchor = AnchorEntity(plane: .horizontal, // 水平面のアンカーエンティティを作る
                          classification: .table, // 平面分類は、 .floor や .any など設定可能
                          minimumBounds: [0.2,0.2])  // 検出最小面積（省略可）
let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.1, 0.03, 0.05)))
anchor.addChild(box) // ボックスをアンカーエンティティの子にする
arView.scene.anchors.append(anchor)
```

### 垂直面アンカー

壁やドアなどをアンカーにできます。

【ディスプレイに垂直面アンカーでボックスを配置】
![IMG_5136.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/dde1cb66-6220-d992-562a-065880f3f5f2.png)
【実装方法】

```swift
let anchor = AnchorEntity(plane: .vertical) // 垂直面のアンカーエンティティを作る
let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.1, 0.03, 0.05)))
anchor.addChild(box) // ボックスをアンカーエンティティの子にする
arView.scene.anchors.append(anchor)
```

### 画像アンカー

ポスターや雑誌の表紙などをアンカーにできます。

【ディスプレイに映った画像を認識してボックスを配置】
![IMG_5156.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/3dbdb43e-c60b-aebe-5de3-6c0ea4835640.jpeg)

【実装方法】

アンカーにしたい画像を登録します。

１、「Assets.xcassets」 → 左下の「+」ボタン → 「AR and Textures」 →　「AR Resource Group」 で ARリソースのフォルダをつくり、アンカーにしたい画像を ドラッグ & ドロップする。
２、アンカー画像をクリックし、右側のペインでアンカー画像の幅と高さを登録する。
![スクリーンショット 2021-06-12 9.25.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/220e10d1-9946-d7f6-0d7e-d8ce95968c0e.png)
３、ARリソース・フォルダ名と画像名からアンカーをつくる。

```swift
let anchor = AnchorEntity(.image(group: "AR Resources", name: "girlImage"))
 // ARリソースのグループ名と画像名を指定して画像アンカーをつくる
let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.1, 0.03, 0.05)))
anchor.addChild(box)
arView.scene.anchors.append(anchor)
```

**【Demo パソコン画面から飛び出すビデオ】**

![Jun-12-2021 12-54-44](https://user-images.githubusercontent.com/23278992/121764235-6e7d7b80-cb7d-11eb-8c44-128515809946.gif)

動画の最初のフレームを画像アンカーとして設定して、アンカー画像と同じサイズのボックス・エンティティに動画を貼り付けて再生しています。

【実装方法】

##### １、動画ボックスの準備

```swift
let anchor = AnchorEntity(.image(group: "AR Resources", name: "girlImage"))
let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.595, 0.001, 0.325)))
 // オリジナルのアンカー画像と同じサイズのボックスをつくる（iMacのディスプレイサイズ）

// 動画マテリアルをつくってはりつける
guard let videoURL = Bundle.main.url(forResource: "girl", withExtension: "mp4") else {return}
let asset = AVURLAsset(url: videoURL)
let playerItem = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: playerItem)
let videoMaterial = VideoMaterial(avPlayer: player)
box.model?.materials = [videoMaterial]

// デリゲートメソッドで操作できるようにクラス変数として保持しておく
videoBoxEntity = box
avPlayer = player
anchor.addChild(box)
arView.scene.anchors.append(anchor)
```

##### ２、アンカーを見つけたときに再生する

```swift
var videoBoxEntity:ModelEntity?
var avPlayer:AVPlayer?

func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
 // セッションに新しいアンカーが発見されたとき実行されるメソッド
    for anchor in anchors {
        guard let imageAnchor = anchor as? ARImageAnchor else { continue }
         // イメージアンカーが見つかった場合
        guard let videoBox = videoBoxEntity else { continue }
        if imageAnchor.name == "girlImage" { // 見つかったアンカーの名前を確認する
             guard let videoMaterial = videoBox.model?.materials.first as? VideoMaterial else {return}
             // 動画のボックスを拡大、移動
             videoBox.move(to: Transform(scale: simd_make_float3(1, 100, 1),
                                         rotation: videoBox.orientation,
                                         translation: simd_make_float3(videoBox.transform.translation.x,
                                                                       videoBox.transform.translation.y+0.7,
                                                                       videoBox.transform.translation.z)),
                          relativeTo: videoBox.parent,
                          duration: 3)
              

            avPlayer?.play() // 動画を再生
       }
   }
}
```

### オブジェクト・アンカー

事前にスキャンしたオブジェクトをアンカーにできます。

【植物のポットをアンカーにしてボックスを配置】
![IMG_5164.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/3eb555e0-a2b9-6e8a-9456-57c334f01b8d.png)

【実装方法】

１、ARKitのスキャン機能でアンカーにしたいオブジェクトをスキャンする

```swift
let configuration = ARObjectScanningConfiguration()
configuration.planeDetection = .horizontal
sceneView.session.run(configuration, options: .resetTracking)
```

```swift
sceneView.session.createReferenceObject(
    transform: boundingBox.simdWorldTransform,
    center: float3(), extent: boundingBox.extent,
    completionHandler: { object, error in
        // 生成された参照オブジェクトをここで処理する
    }
)
```

[アップルのサンプル・アプリ] (https://developer.apple.com/documentation/arkit/content_anchors/scanning_and_detecting_3d_objects)を実行すると、 .arobject ファイルをつくることができます。

![Jun-12-2021 15-20-00](https://user-images.githubusercontent.com/23278992/121767196-d473fe00-cb91-11eb-8675-0bf6e100691a.gif)

２、生成された .arobject ファイルを AR and Textures　のリソース・グループに登録する（手順は画像アンカーと同じ）

![スクリーンショット 2021-06-12 15.10.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/89c8dba4-ba95-90e6-60e3-cc9dd3685644.png)

３、登録したリソース・グループ名と .arobject の名前でオブジェクト・アンカーをつくる

```swift
let anchor = AnchorEntity(.object(group: "AR Resources", name: "pot"))
let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.1, 0.03, 0.05)))
anchor.addChild(box)
arView.scene.anchors.append(anchor)
```

半透明の球体で包んでみました。

![IMG_5170のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/11043dc5-cee2-3fd7-dd3b-6d4ff8b3ec00.png)

```swift
let anchor = AnchorEntity(.object(group: "AR Resources", name: "pot"))
let sphere = ModelEntity(mesh: .generateSphere(radius: 0.1))
let material = SimpleMaterial(color: .green.withAlphaComponent(0.3), roughness: .float(0), isMetallic: true)
sphere.model?.materials = [material]
anchor.addChild(sphere)
arView.scene.anchors.append(anchor)
```

### 顔アンカー

【顔アンカーにガイコツを配置】
![Jun-13-2021 15-23-18](https://user-images.githubusercontent.com/23278992/121797476-50864880-cc5b-11eb-8e67-8269b0f1cb14.gif)

【実装方法】

顔がターゲットのアンカー・エンティティをつかうには、ARFaceTrackingConfiguration で ARView　のセッションを実行します。

```swift
let config = ARFaceTrackingConfiguration() 
arView.session.run(config, options: [])
// この構成でセッションを実行すると、フロントカメラに切り替わります。

let anchor = AnchorEntity(.face)
let usdzModel = try! Entity.loadModel(named: "skull")
anchor.addChild(usdzModel)
arView.scene.anchors.append(anchor)
```

デフォルトでは顔のジオメトリがオクルージョンされ、アンカーが顔に追従します。

### からだアンカー

【からだアンカーの１m上に飛行機のモデルを配置】
// ![Jun-13-2021 21-46-28](https://user-images.githubusercontent.com/23278992/121807845-d5d82000-cc90-11eb-850d-334c22967b0f.gif)

【実装方法】

体アンカーをつかうには、ARBodyTrackingConfiguration で ARView　のセッションを実行します。

```swift
let config = ARBodyTrackingConfiguration()
arView.session.run(config, options: [])

let anchor = AnchorEntity(.body)

let usdzModel = try! Entity.load(named: "toy_biplane")
usdzModel.position = simd_make_float3(0, 1, 0)
anchor.addChild(usdzModel)
arView.scene.anchors.append(anchor)
```

### ARアンカー

ARKit の AR アンカーからアンカー・エンティティをつくれます。
体アンカーに相対する頭や手の位置、顔アンカーの表情の動きなど、AR アンカーのプロパティにアクセスすることもできます。
AR アンカーからつくったアンカー・エンティティは、 AR アンカーの位置のアップデートに追従します。

```swift
let arAnchorEntity = AnchorEntity(anchor: arAnchor)
```


# [6.アニメーション](#もくじ)

移動、回転、拡大縮小をアニメーションできます。
USDZに組み込まれたアニメーションを再生することもできます。

### 移動

【横に移動】
![Jun-13-2021 13-06-33](https://user-images.githubusercontent.com/23278992/121795076-3b53ee80-cc48-11eb-9a5f-f4591c46a508.gif)

【実装方法】

```swift
let anchor = AnchorEntity(world: simd_make_float3(0, 0, -0.2))
let box = ModelEntity(mesh: .generateBox(size: simd_make_float3(0.02, 0.01, 0.01),cornerRadius: 0.001))
anchor.addChild(box)
arView.scene.anchors.append(anchor)

let newTranslation = simd_make_float3(anchor.transform.translation.x+0.05,
                                      anchor.transform.translation.y,
                                      anchor.transform.translation.z)
 // 移動先は、アンカーの元の位置から５cm右に移動した位置
let moveController = anchor.move(to: Transform(scale: simd_make_float3(1, 1, 1), // スケールは変えない
                          rotation: anchor.orientation, // 角度も変えない
                          translation: newTranslation), // 位置だけ置き換える
            relativeTo: anchor, // アンカーが属する座標系において動かす
            duration: 3, // 3秒で動かす
            timingFunction: .linear) // 線形アニメーション（.easeInOut などのカーブも設定可能）

 // relativeTo: を nil にすると、ワールド座標系の変換になります。
 // *アンカー・エンティティを動かしていますが、box のモデル・エンティティを .move　することもできます。
 // *scaleやrotationは変えないのであれば、省略することもできます。
```


### 回転

【150度回転】
![Jun-13-2021 13-10-48](https://user-images.githubusercontent.com/23278992/121795146-d3ea6e80-cc48-11eb-8552-cc0214278cc4.gif)

【実装方法】

```swift
let rotation:Float = 150 * .pi / 180
anchor.move(to: Transform(pitch: 0, yaw: 0, roll: rotation), // z軸を中心に、反時計回りに１５０度
            relativeTo: anchor,
            duration: 1)
```

### 拡大・縮小

【3倍に拡大】
![Jun-13-2021 22-18-33.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/967443e1-8d0a-886b-dfaa-917b47ed1448.gif)

【実装方法】

```swift
let scale = simd_make_float3(3, 3, 3) // 3倍のスケールにする
anchor.move(to: Transform(scale: scale, rotation: anchor.orientation, translation: anchor.transform.translation),
            relativeTo: anchor,
            duration: 1)
```

 *RealityKit2 から .move　メソッドは .moveCharacter メソッドにかわるようです。

### USDZアニメーション

【USDZ ファイルにベイクされたアニメーションを再生】
![Jun-13-2021 21-39-29](https://user-images.githubusercontent.com/23278992/121807789-9e697380-cc90-11eb-97fe-ff2227f9a18a.gif)

Entity.load() で アニメーションごとUSDZ をよみこみ、シーンにエンティティを追加したあとにアニメーションを再生します。

【実装方法】

```swift
let anchor = AnchorEntity()
anchor.position = simd_make_float3(0, 0, -0.5)
let usdzModel = try! Entity.load(named: "toy_biplane")

 // Entity.loadModel() ではアニメーションがロードされないので注意！

anchor.addChild(usdzModel)
arView.scene.anchors.append(anchor)

 // シーンにエンティティを加えたあとに再生しないと、再生されないので注意！

for animation in usdzModel.availableAnimations {
    usdzModel.playAnimation(animation.repeat())
}
```

GIF なのでカクついていますが、実際はなめらかに再生されます。

# [7.照明](#もくじ)
RealityKit はデフォルトで環境の明るさを反映しますが、３種類のライト・エンティティ（ライト・コンポーネント）を加えることもできます。

### ポイント・ライト

全方向におなじ光を放つライト。
家の照明みたいなもの。

【箱の少し手前にポイント・ライトを配置】
![point.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/4e42471a-5b32-5ca9-50aa-f435dbf7fcb6.png)

【実装方法】

```swift
let lightEntity = PointLight()
// 以下 .light でライト・コンポーネントの設定ができる
lightEntity.light.color = .red 
lightEntity.light.intensity = 1000 // ルーメン単位でライトの強さを設定
lightEntity.light.attenuationRadius = 0.5 // ライトが及ぶ半径
lightEntity.look(at: [0,0,0], from: [0,0,0.1], relativeTo: lightAnchor)
 // ライトの位置を１０cm後ろに

let lightAnchor = AnchorEntity(plane: .horizontal)
lightAnchor.addChild(lightEntity)
arView.scene.anchors.append(lightAnchor)
```

### ダイレクショナル・ライト
指向性ライト。ある方向に均一な光を放つ。

【箱の少し手前に指向性ライトを配置】
![directional.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/16c4f6ef-c802-c3d3-40af-55d26b7c81bb.png)

【実装方法】

```swift
let lightEntity = DirectionalLight()
lightEntity.light.color = .red
lightEntity.light.intensity = 1000
lightEntity.light.isRealWorldProxy = true
lightEntity.shadow?.maximumDistance = 10
lightEntity.shadow?.depthBias = 5

let lightAnchor = AnchorEntity(plane: .horizontal)
lightAnchor.addChild(lightEntity)
arView.scene.anchors.append(lightAnchor)
```


### スポット・ライト

円錐形に照らすライト。
舞台照明でよくあるやつ。

【箱の少し手前にスポット・ライトを配置】
![spot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/a57fd2da-cd85-5f1b-d6ee-7056fdfc3a66.png)

【実装方法】

```swift
let lightEntity = SpotLight()
lightEntity.light.color = .red
lightEntity.light.intensity = 3000
lightEntity.look(at: [0,0,0], from: [0,0.05,0.3], relativeTo: lightAnchor)
lightEntity.shadow = SpotLightComponent.Shadow() // デフォルトではシャドウがない
// 以下デフォルト値
lightEntity.light.innerAngleInDegrees = 45
lightEntity.light.outerAngleInDegrees = 60 // これら２つでスポットライトの円の広さと集中度が決まるはず
lightEntity.light.attenuationRadius = 10

let lightAnchor = AnchorEntity(plane: .horizontal)
lightAnchor.addChild(lightEntity)
arView.scene.anchors.append(lightAnchor)
```


# [8.物理作用](#もくじ)

ぶつかって跳ね返ったり、重力を受けたり、といった物理作用を表現できます。

【物理作用をつけた板と球体】
![Jun-15-2021 02-53-42](https://user-images.githubusercontent.com/23278992/121936871-08634500-cd85-11eb-8fd8-1660466a861a.gif)

【実装方法】

エンティティに物理ボディと衝突形状をつけます。

```swift

let horizontalPlane = ModelEntity(mesh: .generateBox(size: [0.2,0.003,0.2]),
                                             materials: [SimpleMaterial(color: .white,
                                             isMetallic: true)])

horizontalPlane.physicsBody = PhysicsBodyComponent(massProperties: .default, // 質量
                                                   material: .generate(friction: 0.1, // 摩擦係数
                                                                       restitution: 0.1), // 衝突の運動エネルギーの保存率
                                                   mode: .kinematic)     
 // .kinematic モードで物理ボディをつける

horizontalPlane.generateCollisionShapes(recursive: false)
 // 衝突形状をつける。子ノードまで recursive　につけることも可能

worldAnchor.addChild(physicalSphere)

// 添付Gif の verticalPlane も同様です

let colors:[UIColor] = [.red,.orange,.magenta,.yellow,.purple,.white]
for index in 0...5 {
    let physicalSphere = ModelEntity(mesh: .generateSphere(radius: 0.01),
                                     materials: [SimpleMaterial(color: colors[index],
                                     isMetallic: true)])
    physicalSphere.physicsBody = PhysicsBodyComponent(massProperties: .default,
                                                   material: .generate(),
                                                   mode: .dynamic) 
     // .dynamic モードで物理ボディをつける

    physicalSphere.generateCollisionShapes(recursive: false)
    // 衝突形状をつける
    worldAnchor.addChild(physicalSphere)
}
        
arView.scene.anchors.append(model.worldAnchor)
```


**PhysicsBodyMode**
>**.dynamic** : 動いて他の dynamic ボディに力をあたえることができる。自身も受けた力による運動をする。
**.kinematic** : 動いて他の dynamic ボディに力をあたえることができる。自身は受けた力による運動をしない。
**.static** : 動かない。他の dynamic ボディに力をあたえるのは衝突された時。自身は受けた力による運動をしない。

※ .dynamic ボディを持つエンティティは、支えがないと重力を受けて落ちていきます。

### 衝突検知

CollisionComponent をつけたエンティティどうしの衝突を検知できます。

【球体と板の衝突をトリガーにビデオ・マテリアルを再生】
![Jun-21-2021 05-38-29](https://user-images.githubusercontent.com/23278992/122688018-b58dff80-d254-11eb-8b94-28fca4016117.gif)

【実装方法】

Combine で　CollisionEvents をリッスンします。

```swift
import Combine

var collisionSubscribing:Cancellable? // イベントをリッスンするために参照保持が必要だそうです
```

```swift
collisionSubscribing = arView.scene.subscribe(to: CollisionEvents.Began.self) { event in
    print("collision !")
    let entityA = event.entityA as? ModelEntity
    let entityB = event.entityB as? ModelEntity 
     // 衝突したエンティティ
}
```

特定のエンティティの衝突を検知する場合は、

```swift
collisionSubscribing = arView.scene.subscribe(to: CollisionEvents.Began.self,
                                              on: specifiedEntity) { event in
    print("collision of the specified entity ! ")
}
```

**※エンティティが PhysicsBodyComponent を持っている場合は、衝突するエンティティのどちらかが.dynamic タイプのボディを持っていないと検知しません。PhysicsBodyComponent を持っていない場合は CollisionComponent のみで検知できます。**

ちなみに、 ARView のセッションを終了して他の View に遷移するときは、 Cancellable をキャンセルしておかないと、ポインタが解放されずメモリ使用量が増えてしまうので、明示的にキャンセルします。

```swift
collisionSubscribing.cancel()
```

### 衝突フィルター

# [9.よりリアルな現実世界との相互作用](#もくじ)

Lidar 搭載のデバイスでは、現実の物理形状が細かくとれるので、以下の効果をつけて AR　をよりリアルに見せられます。

### オクルージョン

現実の物体の背後に ARオブジェクトが隠れます。

```swift
arView.environment.sceneUnderstanding.options.insert(.occlusion)
```

### 影

AR　オブジェクトが現実の床に影を落とします

```swift
arView.environment.sceneUnderstanding.options.insert(.receivesLighting)
```

### 物理演算

AR オブジェクトが、現実のオブジェクトと物理的にインタラクションします。

```swift
arView.environment.sceneUnderstanding.options.insert(.physics)
```

### 衝突

現実の物体との衝突を検知します。

```swift
arView.environment.sceneUnderstanding.options.insert(.collision)
```

# [10.オーディオ](#もくじ)

音声の再生

【実装方法】

AudioFileResourceで音源を読み込み、エンティティのprepareAudioメソッドに渡すと、AudioPlaybackControllerを返すので、AudioPlaybackControllerで再生します。

```swift
do {
    let audioResource = try AudioFileResource.load(named: "audio.mp3", // バンドル・メインから読み込む
                                                   in: nil,
                                                   inputMode: .spatial, // デバイスの位置によって聞こえ方が変わる
                                                   loadingStrategy: .preload, // .stream でリアルタイムで読み込むことも可能
                                                   shouldLoop: true) // ループさせる

    let audioPlaybackController = entity.prepareAudio(audioResource)
        audioPlaybackController.play()            
} catch {
    print("Error loading audio file")
}

```

AudioFileResource.load(contentOf:URL ...) にすることで、URL からも音源を読み込めます。

##### AudioFileResource.InputMode
| モード |説明  |
|---|---|
|nonSpatial|　位置に関係なく同じ聞こえ方 |
|spatial|　デバイスとエンティティの距離と方向の関係によって聞こえ方が変わる |
|ambient|　デバイスとエンティティの方向の関係によって聞こえ方が変わる |


# [11.タップとジェスチャー](#もくじ)

### タップ

ARView はユーザーのタップの先にあるエンティティを検出できます。

【エンティティをタップしたときに、力を加える】
![Jun-17-2021 11-11-04.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/63c1651f-b1ee-3e1f-78df-6836a3e939d6.gif)

【実装方法】

１、UITapGestureRecognizer で ARView へのユーザーのタップを検出し、その先にあるエンティティを取得する。

```swift
@IBAction func onTap(_ sender: UITapGestureRecognizer) {
    let tapLocation = sender.location(in: arView)

    if let tappedEntity = arView.entity(at: tapLocation) as? ModelEntity {
        tappedEntity.addForce([0,100,-100], relativeTo: nil)
         // y軸、z軸方向に100ニュートンの力をタップしたエンティティに加える
    }
}
```


**２、ヒットテストで検出するエンティティには物理シェイプが必要です。**

```swift
let tappableEntity = ModelEntity(mesh: .generateBox(size: 0.1, cornerRadius: 0.02))
tappableEntity.generateCollisionShapes(recursive: true)
 // recursive に子エンティティにも物理シェイプをつけられる
```

### Ray Cast による平面との交点検出

タップした場所の先にある平面との交点を検出できます。

```swift
@objc func tapped(sender: UITapGestureRecognizer){
    let location = sender.location(in: arView)
    let results = self.raycast(from: location, allowing: .estimatedPlane, alignment: .any)
     //　水平・垂直両方の平面を検出する設定で、ARView上のタップした場所からレイ・キャストする 

    if let firstResult = results.first {
        let anchor = ARAnchor(name: "Anchor for object placement", transform: firstResult.worldTransform)
         // rayCastQuery　の結果の交点座標で AR アンカーをつくる
        arView.session.add(anchor: anchor) // シーンに AR アンカーを加える

        let anchorEntity = AnchorEntity(anchor: anchor)  // AR アンカーからアンカー・エンティティをつくる
        let modelEntity = ModelEntity(mesh: generateBox(size: 0.05))
        modelEntity.model?.materials = [SimpleMaterial(color:　.white, isMetallic: true)]
        anchorEntity.addChild(modelEntity)
        arView.scene.addAnchor(anchorEntity)
    }
}

// エンティティを平面に平行に配置したい場合は、 allowing を　existingPlaneGeometry　にする
// let results = arView.raycast(from: location, allowing: .existingPlaneGeometry, alignment: .any)
```

### ジェスチャーで移動、回転、拡大・縮小

ARView に組み込まれている **エンティティ用のジェスチャ・リコナイザー**（ UIGestureRecognizer のサブクラス）をエンティティごとに ARView にインストールすることで、エンティティを

パン（ドラッグ）・ジェスチャーで 移動（ X*Z 平面 ）
2本指で円を描くジェスチャーで 回転（ Y軸 ）、
ピンチ・ジェスチャーで 拡大・縮小

ができます。

【ジェスチャーで移動・回転・拡大・縮小するボックス】
![Jun-27-2021 22-38-42.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/50fa6cee-f471-852a-098b-6f59fc091ab3.gif)

【実装方法】

モデル・エンティティに衝突形状をつけて、ジェスチャをインストールします。

```swift
modelEntity.generateCollisionShapes(recursive: true)
arView.installGestures(.all, for: modelEntity)
```

**※エンティティに .dynamic タイプの PhysicsBodyComponent をつけると、移動と回転ジェスチャーが機能しなくなります（スケールのみ機能）。**

上記のインストール手順のみで、移動、回転、拡大・縮小がモデル・エンティティに反映されますが、 他の GestureRecognizer 同様、　@objc func でメソッドを追加して、エンティティの移動距離やスケール量などを取れます。

```swift
arView.installGestures(.all, for: modelEntity).forEach { recognizer in
    recognizer.addTarget(self, action: #selector(handleGesture(sender:)))
}

@objc func handleGesture(sender: UIGestureRecognizer){
    if let panGesture = sender as? EntityTranslationGestureRecognizer {
        // panGesture.state
        // panGesture.entity
        // panGesture.translation(in: Entity?) -> SIMD3<Float>?
        // などがとれる。
    }
}
```

# [12.カスタム・コンポーネント](#もくじ)

コンポーネント・プロトコルに準拠したストラクトをつくって、Entity にロジックを持たせることができます。

【実装方法】

```swift
struct CardComponent: Component, Codable {
    var flipUp = false 
}

let cardEntity = try! Entity.loadMode(named: "card")
cardEntity.components[CardComponent.self] = CardComponent()
cardEntity.components[CardComponent.self]?.flipUp = true
```

# [13.複数のデバイスで共有する](#もくじ)

複数のデバイスで AR 画面を見たときに、同じ位置にエンティティがあったり、同じアクションをしたりして、AR 世界を共有できます。

【iPod Touch の操作を iPhone に反映】
![Jun-19-2021 09-30-36](https://user-images.githubusercontent.com/23278992/122625715-8014d500-d0e1-11eb-8e24-9c10a73762b8.gif)

### Collaborative Session

アンカーの位置やエンティティ・コンポーネントの状態、物理状態などを複数デバイスで即時共有できます。

共有サービスにはいろんなネットワークが使えますが、ここではアップルの **MultiPeerConnectivity** フレームワークをつかいます。

 MultiPeerConnectivity についてはこちら。

【実装方法】

##### 1、Local Network （Bonjour services）をアプリに追加
Info.plistにLocal Network Usage DescriprtionとBonjour servicesを追加します。

![スクリーンショット 2021-05-27 4.04.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/6102bd5b-c67d-df37-c7db-e2481808d4f0.png)

Info.plist のBonjour services に自分のサービス名をStringで記述します。
このサービス名が、そのアプリのやりとりの識別子になります。
**Bonjourサービス名はASCII小文字、数字、およびハイフン１５文字以内である必要があります。**
仮に、"my-mc-service"とします。

```swift:Info.plist
<key>NSBonjourServices</key>
    <array>
        <string>_my-mc-service._tcp</string> // サービス名とtcpの前に_（アンダーバー）を入れます
    </array>
```

##### ２、他のピアと接続する

```swift
import MultipeerConnectivity

class ViewController: UIViewController, MCSessionDelegate, MCNearbyServiceAdvertiserDelegate, MCNearbyServiceBrowserDelegate {

    static let serviceType = "my-mc-service"
    var session: MCSession!
    let myPeerID = MCPeerID(displayName: UIDevice.current.name)
    var serviceAdvertiser: MCNearbyServiceAdvertiser!
    var serviceBrowser: MCNearbyServiceBrowser!
    var connectedPeers: [MCPeerID] {
        return session.connectedPeers
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        session = MCSession(peer: myPeerID, securityIdentity: nil, encryptionPreference: .required)
        session.delegate = self
        serviceAdvertiser = MCNearbyServiceAdvertiser(peer: myPeerID, discoveryInfo: nil, serviceType: ViewController.serviceType)
        serviceAdvertiser.delegate = self
        serviceAdvertiser.startAdvertisingPeer()
        
        serviceBrowser = MCNearbyServiceBrowser(peer: myPeerID, serviceType: ViewController.serviceType)
        serviceBrowser.delegate = self
        serviceBrowser.startBrowsingForPeers()

        arView.scene.synchronizationService = try? MultipeerConnectivityService(session: session)
    }

    //MARK:- delegate

    func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) {
        
    }
    
    func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) {
        
    }
    
    func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
        invitationHandler(true, self.session)
        print(peerID)

    }
    func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) {
        browser.invitePeer(peerID, to: session, withContext: nil, timeout: 10)
        print(peerID)
    }
    
    func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
        
    }
    
    func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
    }
    
    func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {

    }
    
    func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
    }
    
    func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {
    }
    
    func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {
    }
}
```

##### ３、Collaborative Session を開始

```swift
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    let config = ARWorldTrackingConfiguration()
    config.isCollaborationEnabled = true
    arView.session.run(config, options: [])
}
```

これで複数のデバイスでアンカーやエンティティが共有されます。

##### Collaborative Session をなるべく早く有効に、また正確に位置を共有するコツ

>・デバイス同士をなるべく近く、似た角度に向けて同じ風景を見られるようにする
・デバイスを動かして、周囲を認識させ、 ARFrame.WorldMappingStatus を .mapped にする
・アンカーの原点と子エンティティのローカル距離を近くしておく（ずれが少なくなる）
・なるべくエンティティごとにこまめにアンカーを作成する
・ただし、エンティティ同士の相対距離をなるべく正確に保ちたい場合は、一つのアンカーにアタッチする
・デリゲートメソッド内でARアンカー位置のアップデートを受け取り、アンカーエンティティの位置を更新する

### エンティティの変更権限

エンティティの変更を他のデバイスに反映させられるのは、エンティティのオーナーだけです。
エンティティの所有者とは、エンティティを作成したデバイスを意味します。
オーナーシップを譲渡することにより、元のオーナーでないデバイスからもエンティティの変更を全体に反映できます。
所有権を引き継ぐために、元の所有者ではないデバイスが所有権要求を送信します。
【実装方法】

```swift
entity.requestOwnership { result in
    if result == .granted { // 許可

    } else { // 拒否

    }
}
```

エンティティのオーナーは、オーナーシップを要求されたときに許可するかどうかを設定できます。

```swift
entity.synchronization?.ownershipTransferMode = .autoAccept // 許可
entity.synchronization?.ownershipTransferMode = .manual // 拒否
```

共有セッション中に、特定のエンティティを共有したくない場合

```swift
hidingEntity.synchronization = nil
```

これでこのエンティティは自分のデバイスにのみ表示されます

### 他のデバイスの位置アンカー

ARParticipantAnchorは、他のデバイスの位置と、 そのデバイス固有のAR セッションの ID を取得できます。

【iPod Touch の ParticipantAchor にテキスト・エンティティをアタッチ】
![Jun-20-2021 08-54-51](https://user-images.githubusercontent.com/23278992/122658198-689f2000-d1a5-11eb-9ad4-4f490fc67be5.gif)

【実装方法】

```swift
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
    for anchor in anchors {
        if let participantAnchor = anchor as? ARParticipantAnchor {
            let anchorEntity = AnchorEntity(anchor: participantAnchor)
            arView.scene.addAnchor(anchorEntity)
        }
    }
}
```

MultipeerConnectivityでAR セッションの ID を交換して peerID（ディスプレイ・ネームをプロパティとしてもつ） にひもづけておくと、アンカーのデバイスの ディスプレイ・ネームが識別できます。

# [14.RealityComposerで手軽にシーンをつくる](#もくじ)

グラフィック・ユーザー・インターフェイスで AR　のシーンをつくって、アプリに組み込むことができます。
オブジェクト、アンカー、アニメーション、音声、などをシーンに追加し、.rcproject ファイルとして xcode プロジェクトで読み込めます。

【Reality Composer 内でシーンを再生】
![Jun-17-2021 10-14-14.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/c106f211-bb21-6845-d386-b8f3ee4fb676.gif)

【Reality Composer でつくったシーンをARView で再生】
![Jun-19-2021 13-42-08.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/3f1dd232-c6d9-d101-a7aa-c0d3b2c8c6bc.gif)

### Reality Composer でシーンをつくる

Reality Composer プロジェクトは、Xcode 右クリック → Open Developer Tool
もしくは、Xcode　の New → File から作成できます。
**また、Xcode にバンドルした .rcproject ファイル画面で Open in Reality Composer をクリックするとReality Composer でファイルが開け、Reality Composer　での編集内容が　Xcode　のプロジェクトにも即時反映されます。**

コンテンツを配置するアンカーを選択できます。
（シーンごとに選択できるアンカーは一つ）

【Reality Composer で選べるアンカー】
![スクリーンショット 2021-06-15 15.38.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/aaf137e4-606e-1ec6-852c-296539fe567d.png)

プリセットのモデルを追加できます。
USDZ モデルも、ドラッグ＆ドロップで追加できます。

【Reality Composer で選べるプリセットのシェイプ】
![スクリーンショット 2021-06-15 15.39.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/dd68eb38-7bc4-702b-eee8-7bacc5e47ece.png)

モデルの位置や大きさ、角度、表面の色、質感、物理特性、衝突特性を設定できます。

【Reality Composer のオブジェクトの設定ペイン】
![スクリーンショット 2021-06-17 9.46.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/a9a4d010-2bfa-d0ef-6ac0-96d3118ddeeb.png)

アニメーションなど、オブジェクトの挙動をシーケンスで設定できます。
以下の挙動を設定できます。

>強調（宙返りなどコミカルなアニメーション）
表示（フェードインなど設定可能）
隠す（フェードアウトなど設定可能）
移動／回転／拡大・縮小（絶対・相対）
力を加える（物理作用を計算してくれる）
オービット（他のオブジェクトの周りをまわる）
シーンを変更
サウンドを再生（プリセット・サウンドがダウンロードできます）
環境音を再生
ミュージックを再生
待機
USDZ アニメーション再生
Xcode で通知アクションをする

各ビヘイビア・シーケンスに以下の開始トリガーを設定できます。

>タップ
シーン開始
カメラが近づいたとき
オブジェクトの衝突
コードからの通知

【Reality Composer のビヘイビア設定】
![スクリーンショット 2021-06-17 10.00.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/ef90857f-2fb9-21cd-712f-9dfb334821ee.png)

【ビヘイビア・シーケンスを再生して確認】
![Jun-17-2021 10-14-14.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/c106f211-bb21-6845-d386-b8f3ee4fb676.gif)


### Xcode で Reality Composer　プロジェクトファイルを読み込む

以下でシーンが再生されます。

```swift
let sceneElement = try! RealityComposerProject.loadScene()
arView.scene.anchors.append(sceneElement)
 // .rcproject ファイル名： RealityComposerProject
 // Scene: シーン名
```

【Reality Composer で作ったシーンを ARView で再生】
![Jun-19-2021 13-42-08.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/3f1dd232-c6d9-d101-a7aa-c0d3b2c8c6bc.gif)

### Reality Composer シーンのエンティティにアクセスする

Reality Composer のプロパティ設定でエンティティの name を設定してアクセスします。

```swift
let sceneElement = try! RealityComposerProject.loadScene()
let box:Entity = sceneElement.box
let robot:Entity = sceneElement.robot
```

### コードからビヘイビアをトリガーする

Reality Composer のビヘイビア設定から通知を選択します。
コードからビヘイビアの name でアクセスします。

```swift
let sceneElement = try! RealityComposerProject.loadScene()
sceneElement.notifications.behavior.post()
 // "behavior"という名前のビヘイビアを発火する
```

# [15.イベントを検知する](#もくじ)
あらかじめ ARView.Scene で特定のイベントをサブスクライブ（購読）することで、イベント発生時にコードを実行できます。
たとえば、アンカーがシーンに固定されたことを受信するには以下。

```swift
import Combine

var cancellables: [Cancellable] = []

let isActiveSub = arView.scene.subscribe(to: SceneEvents.AnchoredStateChanged.self, on: anchorEntity, { event in
    if event.isAnchored {
          print("anchorEntity is active!")
    }
})
cancellables.append(isActiveSub)
```

RealityKit においては以下のイベントが受けとれます。

### Scene Event

| 種類 |説明 |
|---|---|
|SceneEvents.Update　|　フレーム間隔ごとに1回トリガーされるイベントで、フレームごとにカスタムロジックを実行するために使用できます。|
|SceneEvents.AnchoredStateChanged　|　アンカーエンティティのアンカー状態が変更されたときにトリガーされるイベント。|
|AnimationEvents.PlaybackCompleted　|　アニメーションがその期間の終わりに達したときに発生するイベント。|
|AnimationEvents.PlaybackLooped|　アニメーションがループしたときに発生するイベント。|
|AnimationEvents.PlaybackTerminated|　イベントが完了したかどうかに関係なく、イベントが終了したときに発生するイベント。|
|AudioEvents.PlaybackCompleted|　オーディオの再生が完了したときに発生するイベント。|
|CollisionEvents.Began　|　2つのオブジェクトが衝突したときに発生するイベント。 |
|CollisionEvents.Updated　|2つのオブジェクトが接触しているときに、すべてのフレームで発生するイベント。 |
|CollisionEvents.Ended　|　以前に接触していた2つのオブジェクトが分離したときに発生するイベント。|


【画像アンカーがアクティブになったら、ポップアップする絵本】

![Jul-06-2021 11-33-48](https://user-images.githubusercontent.com/23278992/124534020-61873b80-de4e-11eb-906b-1919cc8e06bf.gif)

Combine をつかって、ARView でイベントを受信できます。


【実装例】

```swift
import Combine

var sub:Cancellable! // クラス変数として参照する必要がある
var animated = false
        
sub = arView.scene.subscribe(to: SceneEvents.AnchoredStateChanged.self, on: sceneElement, { event in
    if event.anchor.isActive, !self.animated {
     // アンカーがアクティブになったら少し待ってポップアップする
       self.animated = true
        Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { timer in
          popUpBox.move(to: Transform(translation: [0, 0.2, 0]), relativeTo: popUpBox, duration: 3, timingFunction: .easeInOut)
      }
   }
})
```

Cancellable に当てたイベント検知は明示的にキャンセルするまで、参照が残ってメモリを圧迫するので、使用後はキャンセルします。

```swift
sub.cancel()
```

# [16.ARKitとRealityKitの関係と注意点](#もくじ)

RealityKit は ARKit をベースに構築されており、 **RealityKit 単体でも利用できますが、 各種の Tracking Configuration や ARSessionDelegate をつかう場合は、明示的に　ARKit をインポートしてセッションを構成・実行する必要があります**。

その際、いくつか注意すべきことがあります。

### テクスチャの光の反射


ARKit の WorldTrackingConfiguration をデフォルトの構成でつかうと、RealityKit のマテリアルの反射が暗くなります。

【RealityKit 単体】
![IMG_5648.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/39500461-bb1c-3d9e-0714-33738c473bde.png)

【WorldTrackingConfigration でセッションを実行】
![IMG_5649.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235259/cbd19f96-8ac4-719e-c56f-510bbf88c6b5.png)


これを防ぐには、 ARWorldTrackingConfiguration の environmentTexturing を .automatic に設定します。

```swift
let config = ARWorldTrackingConfiguration()
config.environmentTexturing = .automatic
```

### 平面検出の設定

ARKit で明示的に WorldTrackingConfiguration をつかって、 RealityKit で ターゲット plane のアンカー・エンティティをつかう場合、 WorldTrackingConfiguration の planeDetection を設定しないと、平面にアンカー・エンティティを設置できません。RealityKit 単体使用の場合はこの設定は不要ですが、 ARKit も併用して設定する場合は忘れず設定する必要があります。

```swift
let config = ARWorldTrackingConfiguration()
config.planeDetection = .horizontal
```

# [17.RealityKitをつかうメリット](#もくじ)

iOS でARアプリを作るにはいくつか選択肢がありますが、RealityKitには以下のメリットがあります。

**・アップルの公式かつ最新のフレームワークである**
**・アップデートとサポートが手厚い**
**・あつかいやすい**
**・レンダリングがきれいで、リッチなARコンテンツを表示できる**

ぼくはこれまではARKit+SceneKitでARアプリを作っていましたが、SceneKit は新しい機能追加は止まっているので、この機会にRealityKitをひととおり試してみました。
RealityKitはSceneKitに比べ細かい融通がきかないようなイメージがありましたが、やってみるとぜんぜんいろいろできました。
iOSでARアプリを作る場合の第一候補になると思います。

# [18.RealityKitのサンプルコード集](#もくじ)

RealityKit のサンプルコード集 **「RealityKit-Sampler」** をオープンソースとして公開しました。
GitHub でソースコードを手に入れられます。Xcode でビルドできます。

RealityKit の機能をわかりやすく盛り込んだサンプルコードの集まりです。

### [RealityKit-Sampler](https://github.com/john-rocky/RealityKit-Sampler)

|[<img src="https://user-images.githubusercontent.com/23278992/122830079-cc028c80-d322-11eb-87a3-8aa4803860a4.png" width="150">](https://github.com/john-rocky/RealityKit-Sampler)|[<img src="https://user-images.githubusercontent.com/23278992/124874290-83292400-e002-11eb-948f-13302a5806ed.gif" width="150">](https://github.com/john-rocky/RealityKit-Sampler)|[<img src="https://user-images.githubusercontent.com/23278992/123641992-c8f52800-d85d-11eb-918a-4619071a54c9.gif" width="150">](https://github.com/john-rocky/RealityKit-Sampler)|[<img src="https://user-images.githubusercontent.com/23278992/123644152-11ade080-d860-11eb-828f-68e86ac8ee28.gif" width="150">](https://github.com/john-rocky/RealityKit-Sampler)|
|---|---|---|---|
|[Put A Box](https://github.com/john-rocky/RealityKit-Sampler)|[Giant Robot](https://github.com/john-rocky/RealityKit-Sampler)|[100inch-Monitor](https://github.com/john-rocky/RealityKit-Sampler)|[Building blocks](https://github.com/john-rocky/RealityKit-Sampler)|
||(https://github.com/john-rocky/RealityKit-Sampler)|[<img src="https://user-images.githubusercontent.com/23278992/124336683-e158a000-dbd9-11eb-8a00-5c2943daefcb.gif" width="150">](#)|[<img src="https://user-images.githubusercontent.com/23278992/137721587-cc6e8235-b7a0-4fb1-ad12-4af3c1cbb5e9.gif" width="150">](https://github.com/john-rocky/RealityKit-Sampler)|
|[Speech Balloon](https://github.com/john-rocky/RealityKit-Sampler)|[Special Move](https://github.com/john-rocky/RealityKit-Sampler)|[Face Cropper](https://github.com/john-rocky/RealityKit-Sampler)|[AR Hockey](https://github.com/john-rocky/RealityKit-Sampler)|
|[<img src="https://user-images.githubusercontent.com/23278992/125152071-59e4d100-e185-11eb-8f4c-e14a825ada09.gif" width="150">](https://github.com/john-rocky/RealityKit-Sampler)||||
|[Hand Interaction](https://github.com/john-rocky/RealityKit-Sampler)||||

### 学べること

| コンテンツ | 技術要素 |
| ------------- | ------------- |
| **Put the box**  | SwiftUIのARView, Scene, Entity, Anchor, MeshResource, Material.  |
| **Big Robots**  | USDZ, Animation  |
| **Big Monitor**  | VideoMaterial, SceneEvent  |
| **Building Block** | Ray Cast, Hit Test, Handle Gestures, Physics, Collision, TextureResource |
| **Speech Balloon** | Face Anchor, ARSessionDelegate, RealityComposer |
| **Special Move** | Body Anchor |
| **Face Cropper** | Image Anchor |
| **AR Hockey** | Collaborative Session |
| **Hand Interaction** | addForce, Vision |




#####

🐣
****
こんにちは、だいすけです。
ぼくはフリーランス・エンジニアで、 AR や 機械学習のアプリの実装をしています。
お仕事のご相談こちらまで。
rockyshikoku@gmail.com

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

[Twitter](https://twitter.com/JackdeS11)
[Medium](https://medium.com/@rockyshikoku)