LWJGL と OpenCV を Clojure で書いていく

# LWJGL と OpenCV を Clojure で書いていく
# 動機
  - Lisp っぽいSyntaxで OpenGL とか使ってノベルゲームエンジンを作ってみたい
  - JVM 言語で書けば、どこでも動くんじゃないかな？
  - ゆくゆくは OpenCV + OpenAL も使って動画ファイルを再生したい
  - JavaFX...知らない子ですねぇ

# 出来たこと
  - （OpenCV を Clojure で使うことが出来た）<= まだクロスプラットホームに対応させられていない・・・
  - LWJGL を、 Java のチュートリアルを変換することで使うことが出来た   
  [レポジトリはこちらです](https://github.com/MokkeMeguru/clj-lwjgl-vplayer/tree/qiita-1)


# 実装方法
  注意：ここの実装方法は、OpenCV の項をクリアした上でなければ動きません・
## Project.clj
   ここが一番重要な部分だと思います。僕ではうまく実装することが出来なかったので、[[こちら](https://github.com/rogerallen/hello_lwjgl)] を参考にさせていただきました。
   (おそらく上のページを踏襲してOpenCVも実装すればクロスプラットホームにすることが出来ると思いますが、自分の技術力では出来ませんでした)
   また、こちらに関してはまるで何をやっているのかわからないので、解説は出来ません（募：解説）

``` clojure
(require 'leiningen.core.eval)

(def JVM-OPTS
  {:common   []
   :macosx   ["-XstartOnFirstThread" "-Djava.awt.headless=true"]
   :linux    []
   :windows  []})

(defn jvm-opts
  "Return a complete vector of jvm-opts for the current os."
  [] (let [os (leiningen.core.eval/get-os)]
       (vec (set (concat (get JVM-OPTS :common)
                         (get JVM-OPTS os))))))
(def LWJGL_NS "org.lwjgl")

;; Edit this to change the version.
(def LWJGL_VERSION "3.1.5")

;; Edit this to add/remove packages.
(def LWJGL_MODULES ["lwjgl"
                    "lwjgl-assimp"
                    "lwjgl-bgfx"
                    "lwjgl-egl"
                    "lwjgl-glfw"
                    "lwjgl-jawt"
                    "lwjgl-jemalloc"
                    "lwjgl-lmdb"
                    "lwjgl-lz4"
                    "lwjgl-nanovg"
                    "lwjgl-nfd"
                    "lwjgl-nuklear"
                    "lwjgl-odbc"
                    "lwjgl-openal"
                    "lwjgl-opencl"
                    "lwjgl-opengl"
                    "lwjgl-opengles"
                    "lwjgl-openvr"
                    "lwjgl-par"
                    "lwjgl-remotery"
                    "lwjgl-rpmalloc"
                    "lwjgl-sse"
                    "lwjgl-stb"
                    "lwjgl-tinyexr"
                    "lwjgl-tinyfd"
                    "lwjgl-tootle"
                    "lwjgl-vulkan"
                    "lwjgl-xxhash"
                    "lwjgl-yoga"
                    "lwjgl-zstd"])

;; It's safe to just include all native dependencies, but you might
;; save some space if you know you don't need some platform.
(def LWJGL_PLATFORMS ["linux" "macos" "windows"])

;; These packages don't have any associated native ones.
(def no-natives? #{"lwjgl-egl" "lwjgl-jawt" "lwjgl-odbc"
                   "lwjgl-opencl" "lwjgl-vulkan"})

(defn lwjgl-deps-with-natives []
  (apply concat
         (for [m LWJGL_MODULES]
           (let [prefix [(symbol LWJGL_NS m) LWJGL_VERSION]]
             (into [prefix]
                   (if (no-natives? m)
                     []
                     (for [p LWJGL_PLATFORMS]
                       (into prefix [:classifier (str "natives-" p)
                                     :native-prefix ""]))))))))

(def all-dependencies
  (into ;; Add your non-LWJGL dependencies here
   '[[org.clojure/clojure "1.8.0"]
     [org.clojure/core.async "0.4.474"]

     ;; this is for Linux
     [opencv/opencv "4.0.0"]
     [opencv/opencv-native "4.0.0"]
     ]
   (lwjgl-deps-with-natives)))


(defproject clj-lwjgl-vplayer "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies ~all-dependencies
  :injections [(clojure.lang.RT/loadLibrary org.opencv.core.Core/NATIVE_LIBRARY_NAME)]
  :jvm-opts ^:replace ~(jvm-opts)
  )
```

## OpenCV
   [ここ](https://docs.opencv.org/2.4/doc/tutorials/introduction/clojure_dev_intro/clojure_dev_intro.html) をそのまま使いました。以下は自分がやったことをまとめたものです。
   
### OpenCV を Clojure で使う
* Clojure 環境をセットアップしましょう    
     Clojureの環境をあなたの開発環境（ex. Mac, Linux）にインストールしてください    
     - [ここ](https://github.com/technomancy/leiningen#installation) に従って Leiningen をインストールしましょう    
     それが終わったら、新しく作業フォルダ (例えば simple-sample) を作成し、そこで ~lein run~ コマンドが実行できることを確認してください。

``` shell
$ cd path/to/simple-sample
$ lein run
     
user=>
```

* OpenCV をダウンロードしてビルドしましょう    
  以下の例に従って OpenCV をビルドしてください

``` shell
$ mkdir ~/opt
$ cd ~/opt 
$ git clone https://github.com/opencv/opencv.git
$ cd opencv
$ git checkout 3.4.0
$ mkdir build
$ cd build
$ cmake -DBUILD_SHARED_LIBS=OFF ..
$ make -j8
```

* Leiningen plugin のツールである localrepo をインストールしましょう  

``` shell
$ cd ~/.lein
```
 もし ~/.lein フォルダがない場合には作成してください。

``` shell
$ mkdir ~/.lein
$ cd ~/.lein
```
 次に、profiles.clj を作成して以下の文を追加してください。

``` clojure
{:user {:plugins [[lein-localrepo "LATEST"]]}}
```

 既にファイルがある場合には追記してください。(以下は例)

``` clojure
     {:user {:plugins [[lein-cljsbuild "LATEST"]
                  [lein-figwheel "LATEST"]
                  [lein-localrepo "LATEST"] ;; ここ 
                  [luminus/lein-template "2.9.9.2"]]
        :dependencies [[org.clojure/tools.nrepl "LATEST"]]}}
```
 次に以下のコマンドを実行してください。

``` shell
$ lein deps
```

* OpenCV を Clojure で使えるようにしましょう    
     先程 OpenCV をインストールしたフォルダ =~/opt= にある以下のファイルが必要になります。
     - ./build/bin/opencv-400.jar
     - ./build/lib/libopencv_java400.[so/dll/dylib]     
       Linux で OpenCV をビルドした際には .so 、Mac では dylib 、Windows では dll が見つかると思います。    
       それぞれの環境で必要になりますので、見つかったそれを使いましょう（逆に言うと、このlibファイルが対応していないOSでは動かないので、つまりこれを用いてクロスプラットフォームなアプリケーションを作るのは難しいということになる・・・のかな？）    

   これらのファイルを以下の例に従って配置してください。

``` shell
   $ mkdir ~/opencv-to-native
   $ cp /path/to/opencv-400.jar ~/opencv-to-native
   $ mkdir -p ~/opencv-to-native/native/macosx/x86_64 # 各環境に合わせてください
   $ cp /path/to/libopencv_java400.dylib # 各環境に合わせてください
```

   以下の表に従って、nativeフォルダに適切なファイルを設置してください。


| OS            |    |         |
|:---------------+----+---------:|
| Mac           | -> | macosx  |
| Windows       | -> | windows |
| Linux         | -> | linux   |
| SunOS         | -> | solaris |

| Architectures |    |         |
|:---------------+----+---------:|
| amd64         | -> | x86_64  |
| x86_64        | -> | x86_64  |
| x86           | -> | x86     |
| i386          | -> | x86     |
| arm           | -> | arm     |
| sparc         | -> | sparc   |

* native library を jar ファイルにパッケージ化しましょう    
     以下のコマンドを実行して native-jar を作成してください。

``` shell
     $ cd ~/opencv-to-native
     $ jar -cMf opencv-native-400.jar native
```
     
これによって以下のような構造が出来上がるはずです。

``` text
     opencv-to-native 
     |
     |- native 
     |  |- macosx 
     |     |- x86_64
     |        |- libopencv_java400.dylib
     |- opencv-400.jar
     |- opencv-native-400.jar
```

* leiningen に作成した jar ファイルをインストールしましょう
  以下のコマンドを実行してください。

``` shell
     $ cd ~/opencv-to-native
     $ lein localrepo install opencv-400.jar opencv/opencv 4.0.0
     $ lein localrepo install opencv-native-400.jar opencv/opencv-native 4.0.0
```


* 追記
     以上であなたの環境でOpenCVをleiningen から簡単に利用することが出来るようになりました。    
     以下に使用例として project.clj を紹介します。

``` clojure
(defproject simple-sample "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [opencv/opencv "4.0.0"]
                 [opencv/opencv-native "4.0.0"]])
```

* 書いたプログラム    
     [ここ](http://asukiaaa.blogspot.jp/2017/04/clojureopencv32javaxswing.html) にあるコードを写経しました。    
     [書いたもの](https://github.com/MokkeMeguru/clj-lwjgl-vplayer/blob/master/src/clj_lwjgl_vplayer/learn_opencv/clj-cv-example.clj)


## LWJGL
   [ここ](https://github.com/lwjglgamedev/lwjglbook/blob/master/chapter01/src/main/java/org/lwjglb/game/Main.java) のコードを素直にClojureに変換しました。
   
- ネームスペースのあれこれ
     必要だったものを適宜追加していきました。

``` clojure
(ns clj-lwjgl-vplayer.learn-opencv.learn-opengl.first
  (:import (org.lwjgl.opengl GL GL11)
           (org.lwjgl Version)
           (org.lwjgl.glfw GLFW Callbacks
                           GLFWErrorCallback GLFWKeyCallback)))
```
   - パラメータの保持
     グローバル変数などはClojureの場合では atom や ref を使うようです。
     window に協調性のある ref を使っているのは これを使って Exit 等を判定しているからです。

``` clojure
(defonce param (atom {:width 300 ;; 一回宣言すれば良いので defonce を使っています
                      :height 300}))

(defonce window (ref 0))

```

   - run 関数    
     そのままですね。特に難しいことはないと思いますが、static method の書き方 (Class/static-method) という書き方を思い出すのに少し苦労しました。    
     loop 関数は Clojure の標準ライブラリにあるので、loop_ としました。


   - init 関数    
     こちらもほとんど変わりませんが、一部難しかった部分があったので詳しく説明しようと思います。
     以下の Java コードを見てください。

``` java
glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
            if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
                glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
            }
```

   こちら、lambda 式を使っていますね。これ自体はLispの民としてはとてもうれしいのですが、これを Clojure にどう持ってくるのかが大変難しかったです。    
     イメージとしてJavaの lambda 式は **無名のクラスにつく特定の関数を作る (Overrideする)** といった形で、例えばglfwSetKeyCallback の第2引数は GLFWKeyCallback クラスの Invoke となっているようです。（LWJGLのAPI ドキュメントより）
     そのため、Clojure 側としては、
     1. GLFWKeyCallback を proxy で実装する
     2. Invoke 関数を作る
          が必要となります。
          つまり以下のようになるわけです。

``` clojure
     (proxy [GLFWKeyCallback] []
        (invoke [window k scancode action mods]
              (when (and (== k GLFW/GLFW_KEY_ESCAPE)
                         (== action GLFW/GLFW_RELEASE))
                (GLFW/glfwSetWindowShouldClose @window true))))
```

   - loop_ 関数 && -main 関数    
     そのままです。java の　( A | B ) ってビット演算のことなんですね。すっかり忘れていました。    

ソース全体

``` clojure
(ns clj-lwjgl-vplayer.learn-opencv.learn-opengl.first
  (:import (org.lwjgl.opengl GL GL11)
           (org.lwjgl Version)
           (org.lwjgl.glfw GLFW Callbacks
                           GLFWErrorCallback GLFWKeyCallback)))

(defonce param (atom {:width 300
                      :height 300}))

(defonce window (ref 0))

(defn loop_ []
  (GL/createCapabilities)
  (GL11/glClearColor 1.0 0.0 0.0 0.0)
  (while (not (GLFW/glfwWindowShouldClose @window))
    (GL11/glClear
     (bit-or GL11/GL_COLOR_BUFFER_BIT  GL11/GL_DEPTH_BUFFER_BIT))
    (GLFW/glfwSwapBuffers @window)
    (GLFW/glfwPollEvents)))

(defn init []
  (.set (GLFWErrorCallback/createPrint System/err))
  (when (not (GLFW/glfwInit))
    (throw (IllegalStateException. (str "Unable to initialize GLFW"))))
  (GLFW/glfwDefaultWindowHints)
  (GLFW/glfwWindowHint GLFW/GLFW_VISIBLE GLFW/GLFW_FALSE)
  (let [width (:width @param)
        height (:height @param)]
    (dosync
     (ref-set window (GLFW/glfwCreateWindow width height "Hello World!" 0 0)))
    (when (nil? @window)
      (throw (RuntimeException. "Failed to create GLFW window")))
    (GLFW/glfwSetKeyCallback
     @window
     (proxy [GLFWKeyCallback] []
       (invoke [window k scancode action mods]
         (when (and (== k GLFW/GLFW_KEY_ESCAPE)
                    (== action GLFW/GLFW_RELEASE))
           (GLFW/glfwSetWindowShouldClose @window true)))))
    (let [vidmode (GLFW/glfwGetVideoMode (GLFW/glfwGetPrimaryMonitor))]
      (GLFW/glfwSetWindowPos
       @window
       (/ (- (.width vidmode) width) 2)
       (/ (- (.height vidmode) height) 2)))
    (GLFW/glfwMakeContextCurrent @window)
    (GLFW/glfwSwapInterval 1)
    (GLFW/glfwShowWindow @window)))

(defn run []
  (println "Hello LWJGL " +  (Version/getVersion))
  (try
    (init)
    (loop_)
    (finally
      (Callbacks/glfwFreeCallbacks @window)
      (.free (GLFW/glfwSetErrorCallback nil)))))

(defn -main []
  (run))

;; (-main)
```