Kivy Language で3Dテニスコートを描く

#はじめに

こんにちは。
テニスのラリーを３Dシミュレーションするツールを実装することをモチベーションに、
Kivy Languageで３Dのテニスコートを描くスクリプトをご紹介します。
申し訳ありませんが、ここではKivy Languageの環境設定の仕方は割愛させていただきますm(_ _)m

https://github.com/kpiorno/kivy3dgui
↑こちらに掲載されているコードを大いに参考にさせていただきました。ありがとうございますm(_ _)m

https://github.com/miyabb/share_tennis_court.git
↑実装で扱うディレクトリはこちらに載せてあります。

#マウスでぐりぐり動かせるテニスコートを描く
##ディレクトリ構造
上記の参考にしたサイトをforkし、構造はほぼ変えずに使っています。
今回紹介するKivy Languageの３Dモデルでは、描きたいオブジェクト（ここではテニスコート）をメッシュという概念を使って設計しています。.objファイル（３Dモデルデータフォーマット）はそのメッシュのリストを記載しているものです。

```
Tennis/
    ┠ tennis_court.py  #実行するスクリプト
    ┃
    ┠ data/
    ┃    ┠ imgs/  #オブジェクトにコーティングするイメージ画像
    ┃    ┗ obj/   #各オブジェクトのメッシュリスト
    ┃
    ┗ kivy3dgui/  #kivyで3Dを描くためのパッケージ

```


##出力イメージ
メインスクリプトを実行すると、以下の画像のようなテニスコートをマウスでぐりぐり動かせるものが出力されることが、今回のゴールです。

![スクリーンショット 2019-05-16 22.45.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/301579/db218580-1e9d-2281-45b5-e84009c35fdf.png)




##アプリケーション設計

実行するスクリプト（tennis_court.py）の解釈です。
`main`で走らせるアプリケーションクラス `Tennis3dApp`の中身について見ていきます。

`build`関数はアプリケーションに表示するオブジェクトの配置や機能を定義する関数です。

この部分では、マウスで視点を動かす機能を付与する際の、カメラの向きや位置情報の初期値（ここでいうユーザからの視点）を設定しています。後に出てくる`on_touch_move`関数で使われるものです。


```python:tennis_court.py
 
class Tennis3dApp(App):
    
    def build(self):
        #カメラ（視点の向き）
        self.move_camera = True   
        self.cam_distance = 10
        self.super = []
        
        init_dist = []
        rad = 70.0
        azimuth = 0 #0 to 2PI
        polar = 90 #0 to PI
        
        self.m_sx = 0

        x = rad * math.cos(azimuth) * math.sin(polar)                     
        y = rad * math.sin(azimuth) * math.sin(polar)                     
        z = rad * math.cos(polar)

        self.rad = rad
        self.azimuth = azimuth
        self.polar = polar                  

```

その後に続く`layout3d_str`には何やらながーいstringが格納されていますが、ここにはウィンドウ内の空間に設置するオブジェクト（テニスコートの面、ネット、ボール等）の色や配置などの情報が全て書かれています。

`Layout3D`が空間全体のレイアウト、その下にある`Node`たちがその空間の中に配置するオブジェクトを表します。今回描いた`Node`のオブジェクトは上から順に



- 手前の壁（Player A）
- 奥の壁（Player B）
- ボール
- ネット
- テニスコートの面
- テニスコートのライン

となっています。


```python:tennis_court.py        
        self.layout3d_str = '''
                    #:kivy 1.0
                    #: import Layout3D kivy3dgui.layout3d
                    #: import Animation kivy.animation.Animation
                    
                    Layout3D:  #空間の情報。この下は光と影の調節
                        id: board3d
                        #look_at: [0, 0, 10, 0, 0, -20, 0, 1, 0]
                        canvas_size: (1920, 1080)
                        light_position: [-24.5, 150, 100]
                        post_processing: True  

                        shadow_threshold: 0.3 
                        post_processing: True

                        Node:  #手前の壁
                            id: front
                            rotate: (0, 0, 1, 0)     #回転    
                            scale: (1.0, 1.2, 0.8)   #サイズの増減
                            translate: (0, 0, -80)   #移動する向きと大きさ
                            min_light_intensity: 1.0
                            receive_shadows: True                            
                            meshes: ("./data/obj/2dbox.obj",)  
                            Button:
                                id: bottom_floor
                                text: "Player B"
                                font_size: 50
                                background_normal: ''
                                background_color: 0.000 , 0.000 , 0.000, 1.000

                       
                             #######以下省略######
                        '''

```

`Node`オブジェクトで主にいじることの多い属性について、[こちら](https://github.com/kpiorno/kivy3dgui)での解説と日本語での解釈をまとめます。

| Attribute        | Description                                  | 解釈|
|:-----------------|:---------------------------------------------|:--------|
| rotate           |Angle and x, y, z axis of the rotation matrix |回転角度と回転軸 (θ, x, y, z)|
| scale            |x, y, z of scaling matrix                     |スケールの増減 (x, y, z)|
|translate         |x, y, z of translation matrix                 |移動距離と方向（重心を動かす。Defaultは(0, 0, 0)） (x, y, z)|
|meshes            |List of meshes (obj only)                     |形状を定めるobjファイル |


`build`関数の続きです。

```python:tennis_court.py

        layout3d = Builder.load_string(dedent(self.layout3d_str)) #①
        layout3d.bind(on_touch_move = self.on_touch_move) #②    
        self.layout3d = layout3d
        
        instance = GridLayout(cols=2)  #③
        instance.add_widget(self.layout3d)
        self.instance = instance
        
        return self.instance
```
①では先ほど定義した `layout3d`（空間のレイアウト）をBuilderでロードし、
②では、マウスで視点を動かす機能を`bind`で付加しています。
③`instance`は最終的にアプリケーションクラスが返すインスタンスとなります。

その後にある関数２つ（`get_camera_pos`と`on_touch_move`）はマウスでぐりぐり動かすためのおまじない関数で、[参考元](https://github.com/kpiorno/kivy3dgui)を全くいじらずに使ってます。

最後に`main`でクラスを呼び出して終わりです。

# 最後に
Kivyを使ってテニスコートを描くコードを紹介しました。
私自身Kivyの初心者で理解が行き届いていないところもありますが、ほとんど[参考元](https://github.com/kpiorno/kivy3dgui)に掲載されているもので描くことができたので、こちらも参考にしていただければと思います。
もっと説明がほしいところ、解釈の仕方が間違っているところ、ありましたらコメントいただきたいですm(_ _)m



## 参考
https://github.com/kpiorno/kivy3dgui